1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-14 10:13:05 +02:00
pgbackrest/test/lib/pgBackRestTest/Backup/BackupTest.pm
David Steele c8f806a293 Major refactor of the test suite.
* Make the code more modular and object-oriented.
* Multiple Docker containers can now be created for a single test to simulate more realistic environments.
2016-06-24 08:12:58 -04:00

2039 lines
101 KiB
Perl
Executable File

####################################################################################################################################
# BackupTest.pm - Unit Tests for pgBackRest::Backup and pgBackRest::Restore
####################################################################################################################################
package pgBackRestTest::Backup::BackupTest;
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use DBI;
use Exporter qw(import);
use Fcntl ':mode';
use File::Basename qw(dirname);
use File::Copy 'cp';
use File::stat;
use Time::HiRes qw(gettimeofday);
use pgBackRest::Archive;
use pgBackRest::ArchiveInfo;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Ini;
use pgBackRest::Common::Log;
use pgBackRest::Common::Wait;
use pgBackRest::Db;
use pgBackRest::Config::Config;
use pgBackRest::File;
use pgBackRest::FileCommon;
use pgBackRest::Manifest;
use pgBackRest::Protocol::Common;
use pgBackRest::Protocol::RemoteMaster;
use pgBackRest::Version;
use pgBackRestTest::Backup::BackupCommonTest;
use pgBackRestTest::Backup::Common::ExpireCommonTest;
use pgBackRestTest::Backup::Common::HostBackupTest;
use pgBackRestTest::Backup::Common::HostBaseTest;
use pgBackRestTest::Backup::Common::HostDbTest;
use pgBackRestTest::Backup::Common::HostDbSyntheticTest;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::FileTest;
use pgBackRestTest::Common::HostGroupTest;
use pgBackRestTest::Common::HostTest;
use pgBackRestTest::Common::VmTest;
use pgBackRestTest::CommonTest;
####################################################################################################################################
# Archive helper functions
####################################################################################################################################
# Generate an archive log for testing
sub archiveGenerate
{
my $oFile = shift;
my $strXlogPath = shift;
my $iSourceNo = shift;
my $iArchiveNo = shift;
my $bPartial = shift;
my $strArchiveFile = uc(sprintf('0000000100000001%08x', $iArchiveNo)) .
(defined($bPartial) && $bPartial ? '.partial' : '');
my $strArchiveTestFile = testDataPath() . "/test.archive${iSourceNo}.bin";
my $strSourceFile = "${strXlogPath}/${strArchiveFile}";
$oFile->copy(PATH_DB_ABSOLUTE, $strArchiveTestFile, # Source file
PATH_DB_ABSOLUTE, $strSourceFile, # Destination file
false, # Source is not compressed
false); # Destination is not compressed
return $strArchiveFile, $strSourceFile;
}
# Check that an archive log is present
sub archiveCheck
{
my $oFile = shift;
my $strArchiveFile = shift;
my $strArchiveChecksum = shift;
my $bCompress = shift;
# Build the archive name to check for at the destination
my $strArchiveCheck = PG_VERSION_93 . "-1/${strArchiveFile}-${strArchiveChecksum}";
if ($bCompress)
{
$strArchiveCheck .= '.gz';
}
my $oWait = waitInit(5);
my $bFound = false;
do
{
$bFound = $oFile->exists(PATH_BACKUP_ARCHIVE, $strArchiveCheck);
}
while (!$bFound && waitMore($oWait));
if (!$bFound)
{
confess 'unable to find ' . $strArchiveCheck;
}
}
####################################################################################################################################
# backupTestRun
####################################################################################################################################
sub backupTestRun
{
my $strTest = shift;
my $iThreadMax = shift;
my $bVmOut = shift;
# If no test was specified, then run them all
if (!defined($strTest))
{
$strTest = 'all';
}
# Setup global variables
my $oHostGroup = hostGroupGet();
my $strTestPath = $oHostGroup->paramGet(HOST_PARAM_TEST_PATH);
# Setup test variables
my $iRun;
my $strModule = 'backup';
my $strThisTest;
my $strStanza = HOST_STANZA;
my $oLogTest;
my $strArchiveChecksum = '1c7e00fd09b9dd11fc2966590b3e3274645dd031';
my $iArchiveMax = 3;
my $strArchiveTestFile = testDataPath() . '/test.archive2.bin';
my $strArchiveTestFile2 = testDataPath() . '/test.archive1.bin';
pathModeDefaultSet('0700');
fileModeDefaultSet('0600');
# Print test banner
if (!$bVmOut)
{
&log(INFO, 'BACKUP MODULE ******************************************************************');
&log(INFO, "THREAD-MAX: ${iThreadMax}\n");
}
#-------------------------------------------------------------------------------------------------------------------------------
# Test archive-push
#-------------------------------------------------------------------------------------------------------------------------------
$strThisTest = 'archive-push';
if ($strTest eq 'all' || $strTest eq $strThisTest)
{
$iRun = 0;
if (!$bVmOut)
{
&log(INFO, "Test ${strThisTest}\n");
}
for (my $bRemote = false; $bRemote <= true; $bRemote++)
{
for (my $bCompress = false; $bCompress <= true; $bCompress++)
{
for (my $bArchiveAsync = false; $bArchiveAsync <= true; $bArchiveAsync++)
{
# Increment the run, log, and decide whether this unit test should be run
if (!testRun(++$iRun,
"rmt ${bRemote}, cmp ${bCompress}, " .
"arc_async ${bArchiveAsync}",
$iThreadMax == 1 ? $strModule : undef,
$iThreadMax == 1 ? $strThisTest: undef,
\$oLogTest)) {next}
# Create hosts, file object, and config
my ($oHostDbMaster, $oHostBackup, $oFile) = backupTestSetup(
$bRemote, true, $oLogTest, {bCompress => $bCompress, bArchiveAsync => $bArchiveAsync});
# Create the xlog path
my $strXlogPath = $oHostDbMaster->dbBasePath() . '/pg_xlog';
filePathCreate($strXlogPath, undef, false, true);
my $strCommand =
$oHostDbMaster->backrestExe() . ' --config=' . $oHostDbMaster->backrestConfig() .
' --no-fork --stanza=db archive-push';
# Loop through backups
for (my $iBackup = 1; $iBackup <= 3; $iBackup++)
{
my $strArchiveFile;
# Loop through archive files
for (my $iArchive = 1; $iArchive <= $iArchiveMax; $iArchive++)
{
my $strSourceFile;
# Construct the archive filename
my $iArchiveNo = (($iBackup - 1) * $iArchiveMax + ($iArchive - 1)) + 1;
if ($iArchiveNo > 255)
{
confess 'backup total * archive total cannot be greater than 255';
}
($strArchiveFile, $strSourceFile) = archiveGenerate($oFile, $strXlogPath, 2, $iArchiveNo);
&log(INFO, ' backup ' . sprintf('%02d', $iBackup) .
', archive ' .sprintf('%02x', $iArchive) .
" - ${strArchiveFile}");
if ($iBackup == 1 && $iArchive == 2)
{
# Should succeed when temp file already exists
&log(INFO, ' test archive when tmp file exists');
my $strArchiveTmp =
$oHostBackup->repoPath() . "/archive/${strStanza}/" .
PG_VERSION_93 . '-1/' . substr($strArchiveFile, 0, 16) . "/${strArchiveFile}.tmp";
executeTest('sudo chmod 770 ' . dirname($strArchiveTmp));
fileStringWrite($strArchiveTmp, 'JUNK');
}
$oHostDbMaster->executeSimple($strCommand . " ${strSourceFile}", {oLogTest => $oLogTest});
if ($iArchive == $iBackup)
{
# load the archive info file so it can be munged for testing
my $strInfoFile = $oFile->pathGet(PATH_BACKUP_ARCHIVE, ARCHIVE_INFO_FILE);
executeTest("sudo chmod 660 ${strInfoFile}");
my %oInfo;
iniLoad($strInfoFile, \%oInfo);
my $strDbVersion = $oInfo{&INFO_ARCHIVE_SECTION_DB}{&INFO_ARCHIVE_KEY_DB_VERSION};
my $ullDbSysId = $oInfo{&INFO_ARCHIVE_SECTION_DB}{&INFO_ARCHIVE_KEY_DB_SYSTEM_ID};
# Break the database version
$oInfo{&INFO_ARCHIVE_SECTION_DB}{&INFO_ARCHIVE_KEY_DB_VERSION} = '8.0';
testIniSave($strInfoFile, \%oInfo, true);
&log(INFO, ' test db version mismatch error');
$oHostDbMaster->executeSimple(
$strCommand . " ${strSourceFile}",
{iExpectedExitStatus => ERROR_ARCHIVE_MISMATCH, oLogTest => $oLogTest});
# Break the system id
$oInfo{&INFO_ARCHIVE_SECTION_DB}{&INFO_ARCHIVE_KEY_DB_VERSION} = $strDbVersion;
$oInfo{&INFO_ARCHIVE_SECTION_DB}{&INFO_ARCHIVE_KEY_DB_SYSTEM_ID} = '5000900090001855000';
testIniSave($strInfoFile, \%oInfo, true);
&log(INFO, ' test db system-id mismatch error');
$oHostDbMaster->executeSimple(
$strCommand . " ${strSourceFile}",
{iExpectedExitStatus => ERROR_ARCHIVE_MISMATCH, oLogTest => $oLogTest});
# Move settings back to original
$oInfo{&INFO_ARCHIVE_SECTION_DB}{&INFO_ARCHIVE_KEY_DB_SYSTEM_ID} = $ullDbSysId;
testIniSave($strInfoFile, \%oInfo, true);
# Fail because the process was killed
if ($iBackup == 1 && !$bCompress)
{
&log(INFO, ' test stop');
if ($bArchiveAsync)
{
my $oExecArchive =
$oHostDbMaster->execute(
$strCommand . ' --test --test-delay=5 --test-point=' .
lc(TEST_ARCHIVE_PUSH_ASYNC_START) . "=y ${strSourceFile}",
{oLogTest => $oLogTest, iExpectedExitStatus => ERROR_TERM});
$oExecArchive->begin();
$oExecArchive->end(TEST_ARCHIVE_PUSH_ASYNC_START);
$oHostDbMaster->stop({bForce => true});
$oExecArchive->end();
}
else
{
$oHostDbMaster->stop({strStanza => $oHostDbMaster->stanza()});
}
$oHostDbMaster->executeSimple(
$strCommand . " ${strSourceFile}",
{oLogTest => $oLogTest, iExpectedExitStatus => ERROR_STOP});
$oHostDbMaster->start({strStanza => $bArchiveAsync ? undef : $strStanza});
}
# Should succeed because checksum is the same
&log(INFO, ' test archive duplicate ok');
$oHostDbMaster->executeSimple($strCommand . " ${strSourceFile}", {oLogTest => $oLogTest});
# Now it should break on archive duplication (because checksum is different
&log(INFO, ' test archive duplicate error');
($strArchiveFile, $strSourceFile) = archiveGenerate($oFile, $strXlogPath, 1, $iArchiveNo);
$oHostDbMaster->executeSimple(
$strCommand . " ${strSourceFile}",
{iExpectedExitStatus => ERROR_ARCHIVE_DUPLICATE, oLogTest => $oLogTest});
if ($bArchiveAsync)
{
my $strDuplicateWal =
($bRemote ? $oHostDbMaster->spoolPath() :
$oHostBackup->repoPath()) .
"/archive/${strStanza}/out/${strArchiveFile}-4518a0fdf41d796760b384a358270d4682589820";
fileRemove($strDuplicateWal);
# or confess "unable to remove duplicate WAL segment created for testing: ${strDuplicateWal}";
}
# Test .partial archive
&log(INFO, ' test .partial archive');
($strArchiveFile, $strSourceFile) = archiveGenerate($oFile, $strXlogPath, 2, $iArchiveNo, true);
$oHostDbMaster->executeSimple($strCommand . " ${strSourceFile}", {oLogTest => $oLogTest});
archiveCheck($oFile, $strArchiveFile, $strArchiveChecksum, $bCompress);
# Test .partial archive duplicate
&log(INFO, ' test .partial archive duplicate');
$oHostDbMaster->executeSimple($strCommand . " ${strSourceFile}", {oLogTest => $oLogTest});
# Test .partial archive with different checksum
&log(INFO, ' test .partial archive with different checksum');
($strArchiveFile, $strSourceFile) = archiveGenerate($oFile, $strXlogPath, 1, $iArchiveNo, true);
$oHostDbMaster->executeSimple(
$strCommand . " ${strSourceFile}",
{iExpectedExitStatus => ERROR_ARCHIVE_DUPLICATE, oLogTest => $oLogTest});
if ($bArchiveAsync)
{
my $strDuplicateWal =
($bRemote ? $oHostDbMaster->spoolPath() : $oHostBackup->repoPath()) .
"/archive/${strStanza}/out/${strArchiveFile}-4518a0fdf41d796760b384a358270d4682589820";
fileRemove($strDuplicateWal);
# or confess "unable to remove duplicate WAL segment created for testing: ${strDuplicateWal}";
}
}
archiveCheck($oFile, $strArchiveFile, $strArchiveChecksum, $bCompress);
}
# Might be nice to add tests for .backup files here (but this is already tested in full backup)
}
if (defined($oLogTest))
{
$oLogTest->supplementalAdd($oFile->pathGet(PATH_BACKUP_ARCHIVE) . '/archive.info');
}
}
}
}
testCleanup(\$oLogTest);
}
#-------------------------------------------------------------------------------------------------------------------------------
# Test archive-stop
#-------------------------------------------------------------------------------------------------------------------------------
$strThisTest = 'archive-stop';
if ($strTest eq 'all' || $strTest eq $strThisTest)
{
$iRun = 0;
if (!$bVmOut)
{
&log(INFO, "Test ${strThisTest}\n");
}
for (my $bRemote = false; $bRemote <= true; $bRemote++)
{
for (my $bCompress = false; $bCompress <= true; $bCompress++)
{
for (my $iError = 0; $iError <= $bRemote; $iError++)
{
# Increment the run, log, and decide whether this unit test should be run
if (!testRun(++$iRun,
"rmt ${bRemote}, cmp ${bCompress}" .
', error ' . ($iError ? 'connect' : 'version'),
$iThreadMax == 1 ? $strModule : undef,
$iThreadMax == 1 ? $strThisTest: undef,
\$oLogTest)) {next}
# Create hosts, file object, and config
my ($oHostDbMaster, $oHostBackup, $oFile) = backupTestSetup(
$bRemote, true, $oLogTest, {bCompress => $bCompress, bArchiveAsync => true});
# Create the xlog path
my $strXlogPath = $oHostDbMaster->dbBasePath() . '/pg_xlog';
filePathCreate($strXlogPath, undef, false, true);
# Push a WAL segment
$oHostDbMaster->archivePush($strXlogPath, $strArchiveTestFile, 1);
# load the archive info file so it can be munged for testing
my $strInfoFile = $oFile->pathGet(PATH_BACKUP_ARCHIVE, ARCHIVE_INFO_FILE);
executeTest("sudo chmod 660 ${strInfoFile}");
my %oInfo;
iniLoad($strInfoFile, \%oInfo);
my $strDbVersion = $oInfo{&INFO_ARCHIVE_SECTION_DB}{&INFO_ARCHIVE_KEY_DB_VERSION};
my $ullDbSysId = $oInfo{&INFO_ARCHIVE_SECTION_DB}{&INFO_ARCHIVE_KEY_DB_SYSTEM_ID};
# Break the database version
if ($iError == 0)
{
$oInfo{&INFO_ARCHIVE_SECTION_DB}{&INFO_ARCHIVE_KEY_DB_VERSION} = '8.0';
testIniSave($strInfoFile, \%oInfo, true);
}
# Push two more segments with errors to exceed archive-max-mb
$oHostDbMaster->archivePush(
$strXlogPath, $strArchiveTestFile, 2, $iError ? ERROR_HOST_CONNECT : ERROR_ARCHIVE_MISMATCH);
$oHostDbMaster->archivePush(
$strXlogPath, $strArchiveTestFile, 3, $iError ? ERROR_HOST_CONNECT : ERROR_ARCHIVE_MISMATCH);
# Now this segment will get dropped
$oHostDbMaster->archivePush($strXlogPath, $strArchiveTestFile, 4);
# Fix the database version
if ($iError == 0)
{
$oInfo{&INFO_ARCHIVE_SECTION_DB}{&INFO_ARCHIVE_KEY_DB_VERSION} = PG_VERSION_93;
testIniSave($strInfoFile, \%oInfo, true);
}
# Remove the stop file
fileRemove($oHostDbMaster->spoolPath() . '/stop/db-archive.stop');
# Push two more segments - only #4 should be missing from the archive at the end
$oHostDbMaster->archivePush($strXlogPath, $strArchiveTestFile, 5);
$oHostDbMaster->archivePush($strXlogPath, $strArchiveTestFile, 6);
}
}
}
testCleanup(\$oLogTest);
}
#-------------------------------------------------------------------------------------------------------------------------------
# Test archive-get
#-------------------------------------------------------------------------------------------------------------------------------
$strThisTest = 'archive-get';
if ($strTest eq 'all' || $strTest eq $strThisTest)
{
$iRun = 0;
if (!$bVmOut)
{
&log(INFO, "Test ${strThisTest}\n");
}
for (my $bRemote = false; $bRemote <= true; $bRemote++)
{
for (my $bCompress = false; $bCompress <= true; $bCompress++)
{
for (my $bExists = false; $bExists <= true; $bExists++)
{
# Increment the run, log, and decide whether this unit test should be run
if (!testRun(++$iRun,
"rmt ${bRemote}, cmp ${bCompress}, exists ${bExists}",
$iThreadMax == 1 ? $strModule : undef,
$iThreadMax == 1 ? $strThisTest: undef,
\$oLogTest)) {next}
# Create hosts, file object, and config
my ($oHostDbMaster, $oHostBackup, $oFile) = backupTestSetup(
$bRemote, true, $oLogTest, {bCompress => $bCompress});
# Create the xlog path
my $strXlogPath = $oHostDbMaster->dbBasePath() . '/pg_xlog';
filePathCreate($strXlogPath, undef, false, true);
my $strCommand =
$oHostDbMaster->backrestExe() .
' --config=' . $oHostDbMaster->backrestConfig() .
" --stanza=${strStanza} archive-get";
$oHostDbMaster->executeSimple(
$strCommand . " 000000010000000100000001 ${strXlogPath}/000000010000000100000001",
{iExpectedExitStatus => ERROR_FILE_MISSING, oLogTest => $oLogTest});
# Create the archive info file
filePathCreate($oFile->pathGet(PATH_BACKUP_ARCHIVE), '0770', undef, true);
(new pgBackRest::ArchiveInfo($oFile->pathGet(PATH_BACKUP_ARCHIVE)))->check(PG_VERSION_93, 1234567890123456789);
if (defined($oLogTest))
{
$oLogTest->supplementalAdd($oFile->pathGet(PATH_BACKUP_ARCHIVE) . '/archive.info');
}
if ($bExists)
{
# Loop through archive files
my $strArchiveFile;
for (my $iArchiveNo = 1; $iArchiveNo <= $iArchiveMax; $iArchiveNo++)
{
# Construct the archive filename
if ($iArchiveNo > 255)
{
confess 'backup total * archive total cannot be greater than 255';
}
$strArchiveFile = uc(sprintf('0000000100000001%08x', $iArchiveNo));
&log(INFO, ' archive ' .sprintf('%02x', $iArchiveNo) .
" - ${strArchiveFile}");
my $strSourceFile = "${strArchiveFile}-${strArchiveChecksum}";
if ($bCompress)
{
$strSourceFile .= '.gz';
}
filePathCreate(
dirname(
$oFile->pathGet(PATH_BACKUP_ARCHIVE, PG_VERSION_93 . "-1/${strSourceFile}")), '0770', true, true);
$oFile->copy(
PATH_DB_ABSOLUTE, $strArchiveTestFile, # Source file
PATH_BACKUP_ARCHIVE, PG_VERSION_93 . # Destination file
"-1/${strSourceFile}",
false, # Source is not compressed
$bCompress, # Destination compress based on test
undef, undef, # Unused params
'0660', # Mode
true); # Create path if it does not exist
my $strDestinationFile = "${strXlogPath}/${strArchiveFile}";
$oHostDbMaster->executeSimple(
$strCommand . " ${strArchiveFile} ${strDestinationFile}", {oLogTest => $oLogTest});
# Check that the destination file exists
if ($oFile->exists(PATH_DB_ABSOLUTE, $strDestinationFile))
{
if ($oFile->hash(PATH_DB_ABSOLUTE, $strDestinationFile) ne $strArchiveChecksum)
{
confess "archive file hash does not match ${strArchiveChecksum}";
}
}
else
{
confess 'archive file is not in destination';
}
}
}
else
{
$oHostDbMaster->stop();
$oHostDbMaster->executeSimple(
$strCommand . " 000000090000000900000009 ${strXlogPath}/RECOVERYXLOG",
{iExpectedExitStatus => ERROR_STOP, oLogTest => $oLogTest});
$oHostDbMaster->start();
$oHostDbMaster->executeSimple(
$strCommand . " 000000090000000900000009 ${strXlogPath}/RECOVERYXLOG",
{iExpectedExitStatus => 1, oLogTest => $oLogTest});
}
}
}
}
testCleanup(\$oLogTest);
}
#-------------------------------------------------------------------------------------------------------------------------------
# Test expire
#-------------------------------------------------------------------------------------------------------------------------------
$strThisTest = 'expire';
if ($strTest eq 'all' || $strTest eq $strThisTest)
{
$iRun = 0;
if (!$bVmOut)
{
&log(INFO, "Test ${strThisTest}\n");
}
if (testRun(++$iRun,
"local",
$iThreadMax == 1 ? $strModule : undef,
$iThreadMax == 1 ? $strThisTest: undef,
\$oLogTest))
{
# Create hosts, file object, and config
my ($oHostDbMaster, $oHostBackup, $oFile) = backupTestSetup(false, true, $oLogTest);
# Create the test object
my $oExpireTest = new pgBackRestTest::Backup::Common::ExpireCommonTest($oHostBackup, $oFile, $oLogTest);
$oExpireTest->stanzaCreate($strStanza, PG_VERSION_92);
use constant SECONDS_PER_DAY => 86400;
my $lBaseTime = time() - (SECONDS_PER_DAY * 56);
#-----------------------------------------------------------------------------------------------------------------------
my $strDescription = 'Nothing to expire';
$oExpireTest->backupCreate($strStanza, BACKUP_TYPE_FULL, $lBaseTime += SECONDS_PER_DAY);
$oExpireTest->backupCreate($strStanza, BACKUP_TYPE_INCR, $lBaseTime += SECONDS_PER_DAY, 246);
$oExpireTest->process($strStanza, 1, 1, BACKUP_TYPE_FULL, 1, $strDescription);
#-----------------------------------------------------------------------------------------------------------------------
$strDescription = 'Expire oldest full backup, archive expire falls on segment major boundary';
$oExpireTest->backupCreate($strStanza, BACKUP_TYPE_FULL, $lBaseTime += SECONDS_PER_DAY);
$oExpireTest->process($strStanza, 1, 1, BACKUP_TYPE_FULL, 1, $strDescription);
#-----------------------------------------------------------------------------------------------------------------------
$strDescription = 'Expire oldest full backup';
$oExpireTest->backupCreate($strStanza, BACKUP_TYPE_DIFF, $lBaseTime += SECONDS_PER_DAY);
$oExpireTest->backupCreate($strStanza, BACKUP_TYPE_DIFF, $lBaseTime += SECONDS_PER_DAY, 256);
$oExpireTest->process($strStanza, 1, 1, BACKUP_TYPE_FULL, 1, $strDescription);
#-----------------------------------------------------------------------------------------------------------------------
$strDescription = 'Expire oldest diff backup, archive expire does not fall on major segment boundary';
$oExpireTest->backupCreate($strStanza, BACKUP_TYPE_FULL, $lBaseTime += SECONDS_PER_DAY);
$oExpireTest->backupCreate($strStanza, BACKUP_TYPE_DIFF, $lBaseTime += SECONDS_PER_DAY, undef, 0);
$oExpireTest->backupCreate($strStanza, BACKUP_TYPE_INCR, $lBaseTime += SECONDS_PER_DAY, undef, 0);
$oExpireTest->process($strStanza, 1, 1, BACKUP_TYPE_DIFF, 1, $strDescription);
#-----------------------------------------------------------------------------------------------------------------------
$strDescription = 'Expire oldest diff backup (cascade to incr)';
$oExpireTest->backupCreate($strStanza, BACKUP_TYPE_DIFF, $lBaseTime += SECONDS_PER_DAY);
$oExpireTest->process($strStanza, 1, 1, BACKUP_TYPE_DIFF, 1, $strDescription);
#-----------------------------------------------------------------------------------------------------------------------
$strDescription = 'Expire archive based on newest incr backup';
$oExpireTest->backupCreate($strStanza, BACKUP_TYPE_INCR, $lBaseTime += SECONDS_PER_DAY);
$oExpireTest->process($strStanza, 1, 1, BACKUP_TYPE_INCR, 1, $strDescription);
testCleanup(\$oLogTest);
}
}
#-------------------------------------------------------------------------------------------------------------------------------
# Test synthetic
#
# Check the backup and restore functionality using synthetic data.
#-------------------------------------------------------------------------------------------------------------------------------
$strThisTest = 'synthetic';
if ($strTest eq 'all' || $strTest eq $strThisTest)
{
$iRun = 0;
if (!$bVmOut)
{
&log(INFO, "Test ${strModule} - ${strThisTest}\n");
}
for (my $bRemote = false; $bRemote <= true; $bRemote++)
{
for (my $bCompress = false; $bCompress <= true; $bCompress++)
{
for (my $bHardLink = false; $bHardLink <= true; $bHardLink++)
{
# Increment the run, log, and decide whether this unit test should be run
if (!testRun(++$iRun,
"rmt ${bRemote}, cmp ${bCompress}, hardlink ${bHardLink}",
$iThreadMax == 1 ? $strModule : undef,
$iThreadMax == 1 ? $strThisTest: undef,
\$oLogTest)) {next}
# Create hosts, file object, and config
my ($oHostDbMaster, $oHostBackup, $oFile) = backupTestSetup(
$bRemote, true, $oLogTest, {bCompress => $bCompress, bHardLink => $bHardLink, iThreadMax => $iThreadMax});
# Determine if this is a neutral test, i.e. we only want to do it once for local and once for remote. Neutral means
# that options such as compression and hardlinks are disabled
my $bNeutralTest = !$bCompress && !$bHardLink;
# Get base time
my $lTime = time() - 100000;
# Build the manifest
my %oManifest;
$oManifest{&INI_SECTION_BACKREST}{&INI_KEY_VERSION} = BACKREST_VERSION;
$oManifest{&MANIFEST_SECTION_BACKUP_OPTION}{&MANIFEST_KEY_ARCHIVE_CHECK} = JSON::PP::true;
$oManifest{&MANIFEST_SECTION_BACKUP_OPTION}{&MANIFEST_KEY_ARCHIVE_COPY} = JSON::PP::true;
$oManifest{&MANIFEST_SECTION_BACKUP_OPTION}{&MANIFEST_KEY_COMPRESS} = $bCompress ? JSON::PP::true : JSON::PP::false;
$oManifest{&MANIFEST_SECTION_BACKUP_OPTION}{&MANIFEST_KEY_HARDLINK} = $bHardLink ? JSON::PP::true : JSON::PP::false;
$oManifest{&MANIFEST_SECTION_BACKUP_OPTION}{&MANIFEST_KEY_ONLINE} = JSON::PP::false;
$oManifest{&MANIFEST_SECTION_BACKUP_DB}{&MANIFEST_KEY_CATALOG} = 201306121;
$oManifest{&MANIFEST_SECTION_BACKUP_DB}{&MANIFEST_KEY_CONTROL} = 937;
$oManifest{&MANIFEST_SECTION_BACKUP_DB}{&MANIFEST_KEY_SYSTEM_ID} = 6156904820763115222;
$oManifest{&MANIFEST_SECTION_BACKUP_DB}{&MANIFEST_KEY_DB_VERSION} = PG_VERSION_93;
$oManifest{&MANIFEST_SECTION_BACKUP_DB}{&MANIFEST_KEY_DB_ID} = 1;
$oManifest{&MANIFEST_SECTION_BACKUP_TARGET}{&MANIFEST_TARGET_PGDATA}{&MANIFEST_SUBKEY_PATH} =
$oHostDbMaster->dbBasePath();
$oManifest{&MANIFEST_SECTION_BACKUP_TARGET}{&MANIFEST_TARGET_PGDATA}{&MANIFEST_SUBKEY_TYPE} = MANIFEST_VALUE_PATH;
$oHostDbMaster->manifestPathCreate(\%oManifest, MANIFEST_TARGET_PGDATA);
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, DB_FILE_PGVERSION, PG_VERSION_93,
'e1f7a3a299f62225cba076fc6d3d6e677f303482', $lTime);
# Create base path
$oHostDbMaster->manifestPathCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base');
$oHostDbMaster->manifestPathCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/1');
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/1/12000', 'BASE',
'a3b357a3e395e43fcfb19bb13f3c1b5179279593', $lTime);
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/1/' . DB_FILE_PGVERSION,
PG_VERSION_93, 'e1f7a3a299f62225cba076fc6d3d6e677f303482', $lTime);
$oHostDbMaster->manifestPathCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/16384');
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/16384/17000', 'BASE',
'a3b357a3e395e43fcfb19bb13f3c1b5179279593', $lTime);
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/16384/' . DB_FILE_PGVERSION,
PG_VERSION_93, 'e1f7a3a299f62225cba076fc6d3d6e677f303482', $lTime);
$oHostDbMaster->manifestPathCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/32768');
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/32768/33000', '33000',
'7f4c74dc10f61eef43e6ae642606627df1999b34', $lTime);
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/32768/' . DB_FILE_PGVERSION,
PG_VERSION_93, 'e1f7a3a299f62225cba076fc6d3d6e677f303482', $lTime);
# Create global path
$oHostDbMaster->manifestPathCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'global');
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, DB_FILE_PGCONTROL, '[replaceme]',
'56fe5780b8dca9705e0c22032a83828860a21235', $lTime - 100);
# Copy pg_control
executeTest('cp ' . testDataPath() . '/pg_control ' .
$oHostDbMaster->dbBasePath() . '/' . DB_FILE_PGCONTROL);
utime($lTime - 100, $lTime - 100, $oHostDbMaster->dbBasePath() . '/' . DB_FILE_PGCONTROL)
or confess &log(ERROR, "unable to set time");
$oManifest{&MANIFEST_SECTION_TARGET_FILE}{MANIFEST_TARGET_PGDATA . '/' . DB_FILE_PGCONTROL}
{&MANIFEST_SUBKEY_SIZE} = 8192;
# Create tablespace path
$oHostDbMaster->manifestPathCreate(\%oManifest, MANIFEST_TARGET_PGDATA, DB_PATH_PGTBLSPC);
# Backup Info (with no stanzas)
#-----------------------------------------------------------------------------------------------------------------------
$oHostDbMaster->info('no stanzas exist');
$oHostDbMaster->info('no stanzas exist', {strOutput => INFO_OUTPUT_JSON});
# Full backup
#-----------------------------------------------------------------------------------------------------------------------
my $strType = BACKUP_TYPE_FULL;
my $strOptionalParam = '--manifest-save-threshold=3';
my $strTestPoint;
if ($bNeutralTest && $bRemote)
{
$strOptionalParam .= ' --protocol-timeout=2 --db-timeout=1';
if ($iThreadMax > 1)
{
$strTestPoint = TEST_KEEP_ALIVE;
}
}
# Create a file link
filePathCreate($oHostDbMaster->dbPath() . '/pg_config', undef, undef, true);
testFileCreate(
$oHostDbMaster->dbPath() . '/pg_config/postgresql.conf', "listen_addresses = *\n", $lTime - 100);
testLinkCreate($oHostDbMaster->dbPath() . '/pg_config/postgresql.conf.link', './postgresql.conf');
$oHostDbMaster->manifestLinkCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'postgresql.conf',
'../pg_config/postgresql.conf');
# This link will cause errors because it points to the same location as above
$oHostDbMaster->manifestLinkCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'pg_config_bad',
'../../db/pg_config');
my $strFullBackup = $oHostBackup->backup(
$strType, 'error on identical link destinations',
{oExpectedManifest => \%oManifest, strOptionalParam => '--log-level-console=detail',
iExpectedExitStatus => ERROR_LINK_DESTINATION});
# Remove failing link
$oHostDbMaster->manifestLinkRemove(\%oManifest, MANIFEST_TARGET_PGDATA, 'pg_config_bad');
# This link will fail because it points to a link
$oHostDbMaster->manifestLinkCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'postgresql.conf.bad',
'../pg_config/postgresql.conf.link');
# Fail bacause two links point to the same place
$strFullBackup = $oHostBackup->backup(
$strType, 'error on link to a link',
{oExpectedManifest => \%oManifest, strOptionalParam => '--log-level-console=detail',
iExpectedExitStatus => ERROR_LINK_DESTINATION});
# Remove failing link
$oHostDbMaster->manifestLinkRemove(\%oManifest, MANIFEST_TARGET_PGDATA, 'postgresql.conf.bad');
# Create stat directory link and file
filePathCreate($oHostDbMaster->dbPath() . '/pg_stat', undef, undef, true);
$oHostDbMaster->manifestLinkCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'pg_stat', '../pg_stat');
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA . '/pg_stat', 'global.stat', 'stats',
'e350d5ce0153f3e22d5db21cf2a4eff00f3ee877', $lTime - 100);
$oHostDbMaster->manifestPathCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'pg_clog');
$strFullBackup = $oHostBackup->backup(
$strType, 'create pg_stat link, pg_clog dir',
{oExpectedManifest => \%oManifest, strOptionalParam => $strOptionalParam, strTest => $strTestPoint,
fTestDelay => 0});
# Test protocol timeout
#-----------------------------------------------------------------------------------------------------------------------
if ($bNeutralTest && $bRemote)
{
$oHostBackup->backup(
$strType, 'protocol timeout',
{oExpectedManifest => \%oManifest, strOptionalParam => '--protocol-timeout=1 --db-timeout=.1',
strTest => TEST_BACKUP_START, fTestDelay => 1, iExpectedExitStatus => ERROR_PROTOCOL_TIMEOUT});
# Remove the aborted backup so the next backup is not a resume
testPathRemove($oHostBackup->repoPath() . "/temp/${strStanza}.tmp");
}
# Stop operations and make sure the correct error occurs
#-----------------------------------------------------------------------------------------------------------------------
if ($bNeutralTest)
{
# Test a backup abort
my $oExecuteBackup = $oHostBackup->backupBegin(
$strType, 'abort backup - local',
{oExpectedManifest => \%oManifest, strTest => TEST_BACKUP_START, fTestDelay => 5,
iExpectedExitStatus => ERROR_TERM});
$oHostDbMaster->stop({bForce => true});
$oHostBackup->backupEnd($strType, $oExecuteBackup, {oExpectedManifest => \%oManifest});
# Test global stop
$oHostBackup->backup(
$strType, 'global stop',
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_STOP});
# Test stanza stop
$oHostDbMaster->stop({strStanza => $oHostDbMaster->stanza()});
# This time a warning should be generated
$oHostDbMaster->stop({strStanza => $oHostDbMaster->stanza()});
$oHostBackup->backup(
$strType, 'stanza stop',
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_STOP});
$oHostDbMaster->start({strStanza => $strStanza});
$oHostDbMaster->start();
# This time a warning should be generated
$oHostDbMaster->start();
# If the backup is remote then test remote stops
if ($bRemote)
{
my $oExecuteBackup = $oHostBackup->backupBegin(
$strType, 'abort backup - remote',
{oExpectedManifest => \%oManifest, strTest => TEST_BACKUP_START, fTestDelay => 5,
iExpectedExitStatus => ERROR_TERM});
$oHostBackup->stop({bForce => true});
$oHostBackup->backupEnd($strType, $oExecuteBackup, {oExpectedManifest => \%oManifest});
$oHostBackup->backup(
$strType, 'global stop',
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_STOP});
$oHostBackup->start();
}
}
# Cleanup any garbage left in the temp backup path
executeTest(
'sudo rm -rf ' . $oHostBackup->repoPath() . "/temp/${strStanza}.tmp", {bRemote => $bRemote});
# Resume Full Backup
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_FULL;
# These files should never be backed up (this requires the next backup to do --force)
testFileCreate($oHostDbMaster->dbBasePath() . '/' . DB_FILE_POSTMASTERPID, 'JUNK');
testFileCreate($oHostDbMaster->dbBasePath() . '/' . DB_FILE_BACKUPLABELOLD, 'JUNK');
testFileCreate($oHostDbMaster->dbBasePath() . '/' . DB_FILE_RECOVERYCONF, 'JUNK');
testFileCreate($oHostDbMaster->dbBasePath() . '/' . DB_FILE_RECOVERYDONE, 'JUNK');
# Create files in root tblspc paths that should not be copied or deleted.
# This will be checked later after a --force restore.
my $strDoNotDeleteFile = $oHostDbMaster->tablespacePath(1, 2) . '/donotdelete.txt';
filePathCreate(dirname($strDoNotDeleteFile), undef, undef, true);
testFileCreate($strDoNotDeleteFile, 'DONOTDELETE-1-2');
filePathCreate($oHostDbMaster->tablespacePath(1), undef, undef, true);
testFileCreate($oHostDbMaster->tablespacePath(1) . '/donotdelete.txt', 'DONOTDELETE-1');
filePathCreate($oHostDbMaster->tablespacePath(2), undef, undef, true);
testFileCreate($oHostDbMaster->tablespacePath(2) . '/donotdelete.txt', 'DONOTDELETE-2');
filePathCreate($oHostDbMaster->tablespacePath(2, 2), undef, undef, true);
testFileCreate($oHostDbMaster->tablespacePath(2, 2) . '/donotdelete.txt', 'DONOTDELETE-2-2');
my $strTmpPath = $oHostBackup->repoPath() . "/temp/${strStanza}.tmp";
executeTest("sudo chmod g+w " . dirname($strTmpPath));
testPathMove($oHostBackup->repoPath() . "/backup/${strStanza}/${strFullBackup}", $strTmpPath);
my $oMungeManifest = new pgBackRest::Manifest("$strTmpPath/backup.manifest");
$oMungeManifest->remove(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/' . DB_FILE_PGVERSION, 'checksum');
$oMungeManifest->save();
# Create a temp file in backup temp root to be sure it's deleted correctly
executeTest("touch ${strTmpPath}/file.tmp" . ($bCompress ? '.gz' : ''),
{bRemote => $bRemote});
executeTest("sudo chmod -R g+w " . dirname($strTmpPath));
$strFullBackup = $oHostBackup->backup(
$strType, 'resume',
{oExpectedManifest => \%oManifest, strTest => TEST_BACKUP_RESUME, strOptionalParam => '--force'});
# Remove postmaster.pid so restore will succeed (the rest will be cleaned up)
testFileRemove($oHostDbMaster->dbBasePath() . '/' . DB_FILE_POSTMASTERPID);
# Misconfigure repo-path and check errors
#-----------------------------------------------------------------------------------------------------------------------
if ($bNeutralTest)
{
$oHostBackup->backup(
$strType, 'invalid repo',
{oExpectedManifest => \%oManifest, strOptionalParam => '--' . OPTION_REPO_PATH . '=/bogus_path' .
' --log-level-console=detail', iExpectedExitStatus => ERROR_PATH_MISSING});
}
# Restore - tests various mode, extra files/paths, missing files/paths
#-----------------------------------------------------------------------------------------------------------------------
my $bDelta = true;
my $bForce = false;
# Create a path and file that are not in the manifest
$oHostDbMaster->dbPathCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'deleteme');
$oHostDbMaster->dbFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'deleteme/deleteme.txt', 'DELETEME');
# Change path mode
$oHostDbMaster->dbPathMode(\%oManifest, MANIFEST_TARGET_PGDATA, 'base', '0777');
# Change an existing link to the wrong directory
$oHostDbMaster->dbFileRemove(\%oManifest, MANIFEST_TARGET_PGDATA, 'pg_stat');
$oHostDbMaster->dbLinkCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'pg_stat', '../wrong');
# Remove a path
$oHostDbMaster->dbPathRemove(\%oManifest, MANIFEST_TARGET_PGDATA, 'pg_clog');
# Remove a file
$oHostDbMaster->dbFileRemove(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/16384/17000');
$oHostDbMaster->restore(
$strFullBackup, \%oManifest, undef, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'add and delete files', undef, ' --link-all');
# Additional restore tests that don't need to be performed for every permutation
if ($bNeutralTest && !$bRemote)
{
# This time manually restore all links
$oHostDbMaster->restore(
$strFullBackup, \%oManifest, undef, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'restore all links by mapping', undef, '--log-level-console=detail' .
' --link-map=pg_stat=../pg_stat --link-map=postgresql.conf=../pg_config/postgresql.conf');
# Error when links overlap
$oHostDbMaster->restore(
$strFullBackup, \%oManifest, undef, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'restore all links by mapping', ERROR_LINK_DESTINATION, '--log-level-console=warn' .
' --link-map=pg_stat=../pg_stat --link-map=postgresql.conf=../pg_stat/postgresql.conf');
# Error when links still exist on non-delta restore
$bDelta = false;
executeTest('rm -rf ' . $oHostDbMaster->dbBasePath() . "/*");
$oHostDbMaster->restore(
$strFullBackup, \%oManifest, undef, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'error on existing linked path', ERROR_RESTORE_PATH_NOT_EMPTY, '--log-level-console=warn --link-all');
executeTest('rm -rf ' . $oHostDbMaster->dbPath() . "/pg_stat/*");
$oHostDbMaster->restore(
$strFullBackup, \%oManifest, undef, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'error on existing linked file', ERROR_RESTORE_PATH_NOT_EMPTY, '--log-level-console=warn --link-all');
# Now a combination of remapping
$bDelta = true;
$oHostDbMaster->restore(
$strFullBackup, \%oManifest, undef, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'restore all links --link-all and mapping', undef,
'--log-level-console=detail --link-map=pg_stat=../pg_stat --link-all');
}
# Restore - test errors when $PGDATA cannot be verified
#-----------------------------------------------------------------------------------------------------------------------
$bDelta = true;
$bForce = true;
# Remove PG_VERSION
$oHostDbMaster->dbFileRemove(\%oManifest, MANIFEST_TARGET_PGDATA, DB_FILE_PGVERSION);
# Attempt the restore
$oHostDbMaster->restore(
$strFullBackup, \%oManifest, undef, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'fail on missing ' . DB_FILE_PGVERSION, ERROR_RESTORE_PATH_NOT_EMPTY, '--log-level-console=detail');
# Write a backup.manifest file to make $PGDATA valid
testFileCreate($oHostDbMaster->dbBasePath() . '/backup.manifest', 'BOGUS');
# Munge the user to make sure it gets reset on the next run
$oHostBackup->manifestMunge(
$strFullBackup, MANIFEST_SECTION_TARGET_FILE, MANIFEST_FILE_PGCONTROL, MANIFEST_SUBKEY_USER, 'bogus');
$oHostBackup->manifestMunge(
$strFullBackup, MANIFEST_SECTION_TARGET_FILE, MANIFEST_FILE_PGCONTROL, MANIFEST_SUBKEY_GROUP, 'bogus');
# Restore succeeds
$oHostDbMaster->manifestLinkMap(\%oManifest, MANIFEST_TARGET_PGDATA . '/pg_stat');
$oHostDbMaster->manifestLinkMap(\%oManifest, MANIFEST_TARGET_PGDATA . '/postgresql.conf');
$oHostDbMaster->restore(
$strFullBackup, \%oManifest, undef, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'restore succeeds with backup.manifest file', undef, '--log-level-console=detail');
# Various broken info tests
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_INCR;
$oHostDbMaster->manifestReference(\%oManifest, $strFullBackup);
my $strInfoFile = $oHostBackup->repoPath() . "/backup/${strStanza}/backup.info";
executeTest("sudo chmod 660 $strInfoFile");
my %oInfo;
iniLoad($strInfoFile, \%oInfo);
# Break the database version
my $strDbVersion = $oInfo{'db'}{&MANIFEST_KEY_DB_VERSION};
$oInfo{db}{&MANIFEST_KEY_DB_VERSION} = '8.0';
testIniSave($strInfoFile, \%oInfo, true);
$oHostBackup->backup(
$strType, 'invalid database version',
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_BACKUP_MISMATCH,
strOptionalParam => '--log-level-console=detail'});
$oInfo{db}{&MANIFEST_KEY_DB_VERSION} = $strDbVersion;
# Break the database system id
my $ullDbSysId = $oInfo{'db'}{&MANIFEST_KEY_SYSTEM_ID};
$oInfo{db}{&MANIFEST_KEY_SYSTEM_ID} = 6999999999999999999;
testIniSave($strInfoFile, \%oInfo, true);
$oHostBackup->backup(
$strType, 'invalid system id',
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_BACKUP_MISMATCH,
strOptionalParam => '--log-level-console=detail'});
$oInfo{db}{&MANIFEST_KEY_SYSTEM_ID} = $ullDbSysId;
# Break the control version
my $iControlVersion = $oInfo{'db'}{&MANIFEST_KEY_CONTROL};
$oInfo{db}{&MANIFEST_KEY_CONTROL} = 842;
testIniSave($strInfoFile, \%oInfo, true);
$oHostBackup->backup(
$strType, 'invalid control version',
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_BACKUP_MISMATCH,
strOptionalParam => '--log-level-console=detail'});
$oInfo{db}{&MANIFEST_KEY_CONTROL} = $iControlVersion;
# Break the catalog version
my $iCatalogVersion = $oInfo{'db'}{&MANIFEST_KEY_CATALOG};
$oInfo{db}{&MANIFEST_KEY_CATALOG} = 197208141;
testIniSave($strInfoFile, \%oInfo, true);
$oHostBackup->backup(
$strType, 'invalid catalog version',
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_BACKUP_MISMATCH,
strOptionalParam => '--log-level-console=detail'});
# Fix up info file for next test
$oInfo{db}{&MANIFEST_KEY_CATALOG} = $iCatalogVersion;
testIniSave($strInfoFile, \%oInfo, true);
# Test broken tablespace configuration
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_INCR;
my $strTblSpcPath = $oHostDbMaster->dbBasePath() . '/' . DB_PATH_PGTBLSPC;
# Create a directory in pg_tablespace
filePathCreate("${strTblSpcPath}/path");
$oHostBackup->backup(
$strType, 'invalid path in ' . DB_PATH_PGTBLSPC,
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_LINK_EXPECTED,
strOptionalParam => '--log-level-console=detail'});
testPathRemove("${strTblSpcPath}/path");
# Create a relative link in PGDATA
testLinkCreate("${strTblSpcPath}/99999", '../invalid_tblspc');
$oHostBackup->backup(
$strType, 'invalid relative tablespace in $PGDATA',
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_TABLESPACE_IN_PGDATA,
strOptionalParam => '--log-level-console=detail'});
testFileRemove("${strTblSpcPath}/99999");
# Create tablespace in PGDATA
filePathCreate($oHostDbMaster->dbBasePath() . '/invalid_tblspc');
testLinkCreate("${strTblSpcPath}/99999", $oHostDbMaster->dbBasePath() . '/invalid_tblspc');
$oHostBackup->backup(
$strType, 'invalid tablespace in $PGDATA',
{oExpectedManifest => \%oManifest, iExpectedExitStatus => ERROR_TABLESPACE_IN_PGDATA,
strOptionalParam => '--log-level-console=detail'});
testPathRemove($oHostDbMaster->dbBasePath() . '/invalid_tblspc');
testFileRemove("${strTblSpcPath}/99999");
# Incr backup
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_INCR;
# Add tablespace 1
$oHostDbMaster->manifestTablespaceCreate(\%oManifest, 1);
$oHostDbMaster->manifestPathCreate(\%oManifest, MANIFEST_TARGET_PGTBLSPC . '/1', '16384');
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGTBLSPC . '/1', '16384/tablespace1.txt', 'TBLSPC1',
'd85de07d6421d90aa9191c11c889bfde43680f0f', $lTime);
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'badchecksum.txt', 'BADCHECKSUM',
'f927212cd08d11a42a666b2f04235398e9ceeb51', $lTime);
my $strBackup = $oHostBackup->backup(
$strType, 'add tablespace 1', {oExpectedManifest => \%oManifest, strOptionalParam => '--test'});
# Resume Incr Backup
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_INCR;
# Move database from backup to temp
$strTmpPath = $oHostBackup->repoPath() . "/temp/${strStanza}.tmp";
testPathMove($oHostBackup->repoPath() . "/backup/${strStanza}/${strBackup}", $strTmpPath);
executeTest("sudo chmod -R g+w " . dirname($strTmpPath));
$oMungeManifest = new pgBackRest::Manifest("$strTmpPath/" . FILE_MANIFEST);
$oMungeManifest->set(MANIFEST_SECTION_TARGET_FILE, MANIFEST_TARGET_PGDATA . '/badchecksum.txt', 'checksum', 'bogus');
$oMungeManifest->save();
# Add tablespace 2
$oHostDbMaster->manifestTablespaceCreate(\%oManifest, 2);
$oHostDbMaster->manifestPathCreate(\%oManifest, MANIFEST_TARGET_PGTBLSPC . '/2', '32768');
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGTBLSPC . '/2', '32768/tablespace2.txt', 'TBLSPC2',
'dc7f76e43c46101b47acc55ae4d593a9e6983578', $lTime);
$strBackup = $oHostBackup->backup(
$strType, 'resume and add tablespace 2', {oExpectedManifest => \%oManifest, strTest => TEST_BACKUP_RESUME});
# Resume Diff Backup
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_DIFF;
$strTmpPath = $oHostBackup->repoPath() . "/temp/${strStanza}.tmp";
testPathMove($oHostBackup->repoPath() . "/backup/${strStanza}/${strBackup}", $strTmpPath);
executeTest("sudo chmod -R g+w " . dirname($strTmpPath));
$strBackup = $oHostBackup->backup(
$strType, 'cannot resume - new diff',
{oExpectedManifest => \%oManifest, strTest => TEST_BACKUP_NORESUME,
strOptionalParam => '--log-level-console=detail'});
# Resume Diff Backup
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_DIFF;
$strTmpPath = $oHostBackup->repoPath() . "/temp/${strStanza}.tmp";
testPathMove($oHostBackup->repoPath() . "/backup/${strStanza}/${strBackup}", $strTmpPath);
executeTest("sudo chmod -R g+w " . dirname($strTmpPath));
$strBackup = $oHostBackup->backup(
$strType, 'cannot resume - disabled',
{oExpectedManifest => \%oManifest, strTest => TEST_BACKUP_NORESUME,
strOptionalParam => '--no-resume --log-level-console=detail'});
# Restore
#-----------------------------------------------------------------------------------------------------------------------
$bDelta = false;
$bForce = false;
# Fail on used path
$oHostDbMaster->restore(
$strBackup, \%oManifest, undef, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'fail on used path', ERROR_RESTORE_PATH_NOT_EMPTY, '--log-level-console=detail');
# Fail on undef format
$oHostBackup->manifestMunge($strBackup, INI_SECTION_BACKREST, INI_KEY_FORMAT);
$oHostDbMaster->restore(
$strBackup, \%oManifest, undef, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'fail on undef format', ERROR_FORMAT, '--log-level-console=detail');
# Fail on mismatch format
$oHostBackup->manifestMunge($strBackup, INI_SECTION_BACKREST, INI_KEY_FORMAT, undef, 0);
$oHostDbMaster->restore(
$strBackup, \%oManifest, undef, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'fail on mismatch format', ERROR_FORMAT, '--log-level-console=detail');
$oHostBackup->manifestMunge($strBackup, INI_SECTION_BACKREST, INI_KEY_FORMAT, undef, BACKREST_FORMAT);
# Remap the base and tablespace paths
my %oRemapHash;
$oRemapHash{&MANIFEST_TARGET_PGDATA} = $oHostDbMaster->dbBasePath(2);
filePathCreate($oHostDbMaster->dbBasePath(2));
$oRemapHash{&MANIFEST_TARGET_PGTBLSPC . '/1'} = $oHostDbMaster->tablespacePath(1, 2);
$oRemapHash{&MANIFEST_TARGET_PGTBLSPC . '/2'} = $oHostDbMaster->tablespacePath(2, 2);
$oHostDbMaster->restore(
$strBackup, \%oManifest, \%oRemapHash, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'remap all paths', undef, '--log-level-console=detail');
# Restore (make sure file in root tablespace path is not deleted by --delta)
#-----------------------------------------------------------------------------------------------------------------------
$bDelta = true;
$oHostDbMaster->restore(
$strBackup, \%oManifest, \%oRemapHash, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'ensure file in tblspc root remains after --delta', undef, '--log-level-console=detail');
if (!-e $strDoNotDeleteFile)
{
confess "${strDoNotDeleteFile} was deleted by --delta";
}
# Incr Backup
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_INCR;
$oHostDbMaster->manifestReference(\%oManifest, $strBackup);
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/base2.txt', 'BASE2',
'09b5e31766be1dba1ec27de82f975c1b6eea2a92', $lTime);
$oHostDbMaster->manifestTablespaceDrop(\%oManifest, 1, 2);
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGTBLSPC . '/2', '32768/tablespace2b.txt',
'TBLSPC2B', 'e324463005236d83e6e54795dbddd20a74533bf3', $lTime);
# Munge the version to make sure it gets corrected on the next run
$oHostBackup->manifestMunge($strBackup, INI_SECTION_BACKREST, INI_KEY_VERSION, undef, '0.00');
$strBackup = $oHostBackup->backup(
$strType, 'add files and remove tablespace 2',
{oExpectedManifest => \%oManifest, strOptionalParam => '--log-level-console=detail'});
# Incr Backup
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_INCR;
$oHostDbMaster->manifestReference(\%oManifest, $strBackup);
# Delete the backup.info and make sure it gets reconstructed correctly
if ($bNeutralTest)
{
executeTest('sudo rm ' . $oHostBackup->repoPath() . "/backup/${strStanza}/backup.info");
}
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/16384/17000', 'BASEUPDT',
'9a53d532e27785e681766c98516a5e93f096a501', $lTime);
$strBackup =$oHostBackup->backup(
$strType, 'update files',
{oExpectedManifest => \%oManifest, strOptionalParam => '--log-level-console=detail'});
# Diff Backup
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_DIFF;
$oHostDbMaster->manifestReference(\%oManifest, $strFullBackup, true);
$strBackup = $oHostBackup->backup(
$strType, 'updates since last full',
{oExpectedManifest => \%oManifest, strOptionalParam => '--log-level-console=detail'});
# Incr Backup
#
# Remove a file from the db after the manifest has been built but before files are copied. The file will not be shown
# as removed in the log because it had not changed since the last backup so it will only be referenced.
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_INCR;
$oHostDbMaster->manifestReference(\%oManifest, $strBackup);
my $oBackupExecute = $oHostBackup->backupBegin(
$strType, 'remove files - but won\'t affect manifest',
{oExpectedManifest => \%oManifest, strTest => TEST_MANIFEST_BUILD, fTestDelay => 1,
strOptionalParam => '--log-level-console=detail'});
$oHostDbMaster->dbFileRemove(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/16384/17000');
$strBackup = $oHostBackup->backupEnd($strType, $oBackupExecute, {oExpectedManifest => \%oManifest});
# Diff Backup
#
# Remove base2.txt and changed tablespace2c.txt during the backup. The removed file should be logged and the changed
# file should have the new, larger size logged and in the manifest.
#-----------------------------------------------------------------------------------------------------------------------
$oHostDbMaster->manifestReference(\%oManifest, $strFullBackup, true);
$strType = BACKUP_TYPE_DIFF;
$oHostDbMaster->manifestFileRemove(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/16384/17000');
$oHostDbMaster->manifestFileRemove(\%oManifest, MANIFEST_TARGET_PGTBLSPC . '/2', '32768/tablespace2b.txt', true);
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGTBLSPC . '/2', '32768/tablespace2c.txt', 'TBLSPC2C',
'ad7df329ab97a1e7d35f1ff0351c079319121836', $lTime);
$oBackupExecute = $oHostBackup->backupBegin(
$strType, 'remove files during backup',
{oExpectedManifest => \%oManifest, strTest => TEST_MANIFEST_BUILD, fTestDelay => 1,
strOptionalParam => '--log-level-console=detail'});
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGTBLSPC . '/2', '32768/tablespace2c.txt',
'TBLSPCBIGGER', 'dfcb8679956b734706cf87259d50c88f83e80e66', $lTime);
$oHostDbMaster->manifestFileRemove(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/base2.txt', true);
$strBackup = $oHostBackup->backupEnd($strType, $oBackupExecute, {oExpectedManifest => \%oManifest});
# Full Backup
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_FULL;
$oHostDbMaster->manifestReference(\%oManifest);
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/16384/17000', 'BASEUPDT2',
'7579ada0808d7f98087a0a586d0df9de009cdc33', $lTime);
$strFullBackup = $oHostBackup->backup(
$strType, 'update file',
{oExpectedManifest => \%oManifest, strOptionalParam => '--log-level-console=detail'});
# Backup Info
#-----------------------------------------------------------------------------------------------------------------------
$oHostDbMaster->info('normal output', {strStanza => $oHostDbMaster->stanza()});
$oHostBackup->info('normal output', {strStanza => $oHostBackup->stanza(), strOutput => INFO_OUTPUT_JSON});
# Call expire
#-----------------------------------------------------------------------------------------------------------------------
if ($bRemote && !$bCompress)
{
$oHostBackup->expire({iRetentionFull => 1});
}
else
{
$oHostDbMaster->expire(
{iRetentionFull => 1, iExpectedExitStatus => $bRemote && $bCompress ? ERROR_HOST_INVALID : undef});
}
# Diff Backup
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_DIFF;
$oHostDbMaster->manifestReference(\%oManifest, $strFullBackup);
$oHostDbMaster->manifestFileCreate(\%oManifest, MANIFEST_TARGET_PGDATA, 'base/base2.txt', 'BASE2UPDT',
'cafac3c59553f2cfde41ce2e62e7662295f108c0', $lTime);
$strBackup = $oHostBackup->backup(
$strType, 'add file', {oExpectedManifest => \%oManifest, strOptionalParam => '--log-level-console=detail'});
# Selective Restore
#-----------------------------------------------------------------------------------------------------------------------
$bDelta = true;
# Remove mapping for tablespace 1
delete($oRemapHash{&MANIFEST_TARGET_PGTBLSPC . '/1'});
# Remove checksum to match zeroed files
delete($oManifest{&MANIFEST_SECTION_TARGET_FILE}{'pg_data/base/32768/33000'}{&MANIFEST_SUBKEY_CHECKSUM});
delete($oManifest{&MANIFEST_SECTION_TARGET_FILE}{'pg_tblspc/2/PG_9.3_201306121/32768/tablespace2.txt'}
{&MANIFEST_SUBKEY_CHECKSUM});
delete($oManifest{&MANIFEST_SECTION_TARGET_FILE}{'pg_tblspc/2/PG_9.3_201306121/32768/tablespace2c.txt'}
{&MANIFEST_SUBKEY_CHECKSUM});
$oHostDbMaster->restore(
OPTION_DEFAULT_RESTORE_SET, \%oManifest, \%oRemapHash, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'selective restore 16384', undef, '--log-level-console=detail --db-include=16384');
# Restore checksum values for next test
$oManifest{&MANIFEST_SECTION_TARGET_FILE}{'pg_data/base/32768/33000'}{&MANIFEST_SUBKEY_CHECKSUM} =
'7f4c74dc10f61eef43e6ae642606627df1999b34';
$oManifest{&MANIFEST_SECTION_TARGET_FILE}{'pg_tblspc/2/PG_9.3_201306121/32768/tablespace2.txt'}
{&MANIFEST_SUBKEY_CHECKSUM} = 'dc7f76e43c46101b47acc55ae4d593a9e6983578';
$oManifest{&MANIFEST_SECTION_TARGET_FILE}{'pg_tblspc/2/PG_9.3_201306121/32768/tablespace2c.txt'}
{&MANIFEST_SUBKEY_CHECKSUM} = 'dfcb8679956b734706cf87259d50c88f83e80e66';
# Remove chacksum to match zeroed file
delete($oManifest{&MANIFEST_SECTION_TARGET_FILE}{'pg_data/base/16384/17000'}{&MANIFEST_SUBKEY_CHECKSUM});
$oHostDbMaster->restore(
OPTION_DEFAULT_RESTORE_SET, \%oManifest, \%oRemapHash, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'selective restore 32768', undef, '--log-level-console=detail --db-include=32768');
$oManifest{&MANIFEST_SECTION_TARGET_FILE}{'pg_data/base/16384/17000'}{&MANIFEST_SUBKEY_CHECKSUM} =
'7579ada0808d7f98087a0a586d0df9de009cdc33';
$oHostDbMaster->restore(
OPTION_DEFAULT_RESTORE_SET, \%oManifest, \%oRemapHash,
$bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'error on invalid id', ERROR_DB_MISSING, '--log-level-console=warn --db-include=7777');
$oHostDbMaster->restore(
OPTION_DEFAULT_RESTORE_SET, \%oManifest, \%oRemapHash, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'error on system id', ERROR_DB_INVALID, '--log-level-console=warn --db-include=1');
# Compact Restore
#-----------------------------------------------------------------------------------------------------------------------
$bDelta = false;
executeTest('rm -rf ' . $oHostDbMaster->dbBasePath(2) . "/*");
my $strDbPath = $oHostDbMaster->dbBasePath(2) . '/base';
filePathCreate($strDbPath);
$oRemapHash{&MANIFEST_TARGET_PGDATA} = $strDbPath;
delete($oRemapHash{&MANIFEST_TARGET_PGTBLSPC . '/2'});
$oHostDbMaster->restore(
OPTION_DEFAULT_RESTORE_SET, \%oManifest, \%oRemapHash, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'no tablespace remap - error when tablespace dir does not exist', ERROR_PATH_MISSING,
'--log-level-console=detail --tablespace-map-all=../../tablespace', false);
filePathCreate($oHostDbMaster->dbBasePath(2) . '/tablespace');
$oHostDbMaster->restore(
OPTION_DEFAULT_RESTORE_SET, \%oManifest, undef, $bDelta, $bForce, undef, undef, undef, undef, undef, undef,
'no tablespace remap', undef, '--tablespace-map-all=../../tablespace --log-level-console=detail', false);
# Backup Info (with an empty stanza)
#-----------------------------------------------------------------------------------------------------------------------
executeTest('sudo chmod g+w ' . $oHostBackup->repoPath() . '/backup');
filePathCreate($oHostBackup->repoPath() . '/backup/db_empty', '0770');
$oHostBackup->info('normal output');
$oHostDbMaster->info('normal output', {strOutput => INFO_OUTPUT_JSON});
$oHostBackup->info('bogus stanza', {strStanza => 'bogus'});
$oHostDbMaster->info('bogus stanza', {strStanza => 'bogus', strOutput => INFO_OUTPUT_JSON});
# Dump out history path at the end to verify all history files are being recorded. This test is only performed locally
# because for some reason sort order is different when this command is executed via ssh (even though the content of the
# directory is identical).
#-----------------------------------------------------------------------------------------------------------------------
if ($bNeutralTest && !$bRemote)
{
executeTest('ls -1R ' . $oHostBackup->repoPath() . "/backup/${strStanza}/" . PATH_BACKUP_HISTORY,
{oLogTest => $oLogTest, bRemote => $bRemote});
}
# Test protocol shutdown timeout
#-----------------------------------------------------------------------------------------------------------------------
if ($bNeutralTest && $bRemote)
{
$oHostBackup->backup(
$strType, 'protocol shutdown timeout',
{oExpectedManifest => \%oManifest,
strOptionalParam => '--protocol-timeout=1 --db-timeout=.5 --log-level-console=warn',
strTest => TEST_PROCESS_EXIT, fTestDelay => 1, bSupplemental => false});
}
}
}
}
testCleanup(\$oLogTest);
}
#-------------------------------------------------------------------------------------------------------------------------------
# Test full
#
# Check the entire backup mechanism using actual clusters. Only the archive and start/stop mechanisms need to be tested since
# everything else was tested in the backup test.
#-------------------------------------------------------------------------------------------------------------------------------
if ($strTest eq 'all' || $strTest eq 'full')
{
$iRun = 0;
if (!$bVmOut)
{
&log(INFO, "Test Full Backup\n");
}
for (my $bRemote = false; $bRemote <= true; $bRemote++)
{
for (my $bArchiveAsync = false; $bArchiveAsync <= true; $bArchiveAsync++)
{
for (my $bCompress = false; $bCompress <= true; $bCompress++)
{
# Increment the run, log, and decide whether this unit test should be run
if (!testRun(++$iRun, "rmt ${bRemote}, arc_async ${bArchiveAsync}, cmp ${bCompress}")) {next}
# Create hosts, file object, and config
my ($oHostDbMaster, $oHostBackup, $oFile) = backupTestSetup(
$bRemote, false, undef,
{bCompress => $bCompress, iThreadMax => $iThreadMax, bArchiveAsync => $bArchiveAsync});
# For the 'fail on missing archive.info file' test, the archive.info file must not be found so set archive invalid.
$oHostDbMaster->clusterCreate({bArchiveInvalid => true});
# Static backup parameters
my $fTestDelay = 1;
# Variable backup parameters
my $bDelta = true;
my $bForce = false;
my $strType = undef;
my $strTarget = undef;
my $bTargetExclusive = false;
my $strTargetAction;
my $strTargetTimeline = undef;
my $oRecoveryHashRef = undef;
my $strComment = undef;
my $iExpectedExitStatus = undef;
# Restore test string
my $strDefaultMessage = 'default';
my $strFullMessage = 'full';
my $strIncrMessage = 'incr';
my $strTimeMessage = 'time';
my $strXidMessage = 'xid';
my $strNameMessage = 'name';
my $strTimelineMessage = 'timeline3';
# Create two new databases
$oHostDbMaster->sqlExecute('create database test1', {bAutoCommit => true});
$oHostDbMaster->sqlExecute('create database test2', {bAutoCommit => true});
# Test invalid archive command
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_FULL;
# NOTE: This must run before the success test since that will create the archive.info file
$oHostDbMaster->check(
'fail on missing archive.info file',
{iTimeout => 0.1, iExpectedExitStatus => ERROR_FILE_MISSING});
# Stop the cluster ignoring any errors in the postgresql log
$oHostDbMaster->clusterStop({bIgnoreLogError => true});
# Check archive_command_not_set error
$strComment = 'fail on invalid archive_command';
$oHostDbMaster->clusterStart({bArchive => false});
$oHostBackup->backup($strType, $strComment, {iExpectedExitStatus => ERROR_ARCHIVE_COMMAND_INVALID});
$oHostDbMaster->check($strComment, {iTimeout => 0.1, iExpectedExitStatus => ERROR_ARCHIVE_COMMAND_INVALID});
# If running the remote tests then also need to run check locally
if ($bRemote)
{
$oHostBackup->check($strComment, {iTimeout => 0.1, iExpectedExitStatus => ERROR_ARCHIVE_COMMAND_INVALID});
}
# Stop the cluster ignoring any errors in the postgresql log
$oHostDbMaster->clusterStop({bIgnoreLogError => true});
# Providing a sufficient archive-timeout, verify that the check command runs successfully.
$strComment = 'verify success';
$oHostDbMaster->clusterStart();
$oHostDbMaster->check($strComment, {iTimeout => 5});
# If running the remote tests then also need to run check locally
if ($bRemote)
{
$oHostBackup->check($strComment, {iTimeout => 5});
}
# Check archive_timeout error
$strComment = 'fail on archive timeout';
$oHostDbMaster->clusterRestart({bArchiveInvalid => true});
$oHostDbMaster->check($strComment, {iTimeout => 0.1, iExpectedExitStatus => ERROR_ARCHIVE_TIMEOUT});
# If running the remote tests then also need to run check locally
if ($bRemote)
{
$oHostBackup->check($strComment, {iTimeout => 0.1, iExpectedExitStatus => ERROR_ARCHIVE_TIMEOUT});
}
# Restart the cluster ignoring any errors in the postgresql log
$oHostDbMaster->clusterRestart({bIgnoreLogError => true});
# Full backup
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_FULL;
# Create the table where test messages will be stored
$oHostDbMaster->sqlExecute("create table test (message text not null)");
$oHostDbMaster->sqlXlogRotate();
$oHostDbMaster->sqlExecute("insert into test values ('$strDefaultMessage')");
# Acquire the backup advisory lock so it looks like a backup is running
if (!$oHostDbMaster->sqlSelectOne('select pg_try_advisory_lock(' . DB_BACKUP_ADVISORY_LOCK . ')'))
{
confess 'unable to acquire advisory lock for testing';
}
$oHostBackup->backup($strType, 'fail on backup lock exists', {iExpectedExitStatus => ERROR_LOCK_ACQUIRE});
# Release the backup advisory lock so the next backup will succeed
if (!$oHostDbMaster->sqlSelectOne('select pg_advisory_unlock(' . DB_BACKUP_ADVISORY_LOCK . ')'))
{
confess 'unable to acquire advisory lock for testing';
}
my $oExecuteBackup = $oHostBackup->backupBegin(
$strType, 'update during backup',
{strTest => TEST_MANIFEST_BUILD, fTestDelay => $fTestDelay});
$oHostDbMaster->sqlExecute("update test set message = '$strFullMessage'");
my $strFullBackup = $oHostBackup->backupEnd($strType, $oExecuteBackup);
# Execute stop and make sure the backup fails
#-----------------------------------------------------------------------------------------------------------------------
# Restart the cluster to check for any errors before continuing since the stop tests will definitely create errors and
# the logs will to be deleted to avoid causing issues further down the line.
$oHostDbMaster->clusterRestart();
$oHostDbMaster->stop();
$oHostBackup->backup($strType, 'attempt backup when stopped', {iExpectedExitStatus => ERROR_STOP});
$oHostDbMaster->start();
# Setup the time target
#-----------------------------------------------------------------------------------------------------------------------
$oHostDbMaster->sqlExecute("update test set message = '$strTimeMessage'");
$oHostDbMaster->sqlXlogRotate();
my $strTimeTarget = $oHostDbMaster->sqlSelectOne("select to_char(current_timestamp, 'YYYY-MM-DD HH24:MI:SS.US TZ')");
&log(INFO, " time target is ${strTimeTarget}");
# Incr backup - fail on archive_mode=always when version >= 9.5
#-----------------------------------------------------------------------------------------------------------------------
if ($oHostDbMaster->dbVersion() >= PG_VERSION_95)
{
$strType = BACKUP_TYPE_INCR;
# Set archive_mode=always
$oHostDbMaster->clusterRestart({bArchiveAlways => true});
$oHostBackup->backup($strType, 'fail on archive_mode=always', {iExpectedExitStatus => ERROR_FEATURE_NOT_SUPPORTED});
# Reset the cluster to a normal state so the next test will work
$oHostDbMaster->clusterRestart();
}
# Incr backup
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_INCR;
filePathCreate($oHostDbMaster->tablespacePath(1), undef, undef, true);
$oHostDbMaster->sqlExecute(
"create tablespace ts1 location '" . $oHostDbMaster->tablespacePath(1) . "'", {bAutoCommit => true});
$oHostDbMaster->sqlExecute("alter table test set tablespace ts1", {bCheckPoint => true});
$oHostDbMaster->sqlExecute("create table test_remove (id int)");
$oHostDbMaster->sqlXlogRotate();
$oHostDbMaster->sqlExecute("update test set message = '$strDefaultMessage'");
$oHostDbMaster->sqlXlogRotate();
# Start a backup so the next backup has to restart it. This test is not required for PostgreSQL >= 9.6 since backups
# are run in non-exlusive mode.
if ($oHostDbMaster->dbVersion() >= PG_VERSION_93 && $oHostDbMaster->dbVersion() < PG_VERSION_96)
{
$oHostDbMaster->sqlSelectOne("select pg_start_backup('test backup that will be cancelled', true)");
# Verify that an error is returned if the backup is already running
$oHostBackup->backup($strType, 'fail on backup already running', {iExpectedExitStatus => ERROR_DB_QUERY});
# Restart the cluster ignoring any errors in the postgresql log
$oHostDbMaster->clusterRestart({bIgnoreLogError => true});
}
$oExecuteBackup = $oHostBackup->backupBegin(
$strType, 'update during backup',
{strTest => TEST_MANIFEST_BUILD, fTestDelay => $fTestDelay,
strOptionalParam => '--' . OPTION_STOP_AUTO . ' --no-' . OPTION_BACKUP_ARCHIVE_CHECK});
$oHostDbMaster->sqlExecute('drop table test_remove');
$oHostDbMaster->sqlXlogRotate();
$oHostDbMaster->sqlExecute("update test set message = '$strIncrMessage'", {bCommit => true});
my $strIncrBackup = $oHostBackup->backupEnd($strType, $oExecuteBackup);
# Setup the xid target
#-----------------------------------------------------------------------------------------------------------------------
$oHostDbMaster->sqlExecute("update test set message = '$strXidMessage'", {bCommit => false});
$oHostDbMaster->sqlXlogRotate();
my $strXidTarget = $oHostDbMaster->sqlSelectOne("select txid_current()");
$oHostDbMaster->sqlCommit();
&log(INFO, " xid target is ${strXidTarget}");
# Setup the name target
#-----------------------------------------------------------------------------------------------------------------------
my $strNameTarget = 'backrest';
$oHostDbMaster->sqlExecute("update test set message = '$strNameMessage'", {bCommit => true});
$oHostDbMaster->sqlXlogRotate();
if ($oHostDbMaster->dbVersion() >= PG_VERSION_91)
{
$oHostDbMaster->sqlExecute("select pg_create_restore_point('${strNameTarget}')");
}
&log(INFO, " name target is ${strNameTarget}");
# Create a table and data in database test2
#-----------------------------------------------------------------------------------------------------------------------
$oHostDbMaster->sqlExecute(
'create table test (id int);' .
'insert into test values (1);' .
'create table test_ts1 (id int) tablespace ts1;' .
'insert into test_ts1 values (2);',
{strDb => 'test2', bAutoCommit => true});
$oHostDbMaster->sqlXlogRotate();
# Restore (type = default)
#-----------------------------------------------------------------------------------------------------------------------
$bDelta = false;
$bForce = false;
$strType = RECOVERY_TYPE_DEFAULT;
$strTarget = undef;
$bTargetExclusive = undef;
$strTargetAction = undef;
$strTargetTimeline = undef;
$oRecoveryHashRef = undef;
$strComment = undef;
$iExpectedExitStatus = undef;
&log(INFO, " testing recovery type = ${strType}");
# Expect failure because postmaster.pid exists
$strComment = 'postmaster running';
$iExpectedExitStatus = ERROR_POSTMASTER_RUNNING;
$oHostDbMaster->restore(
OPTION_DEFAULT_RESTORE_SET, undef, undef, $bDelta, $bForce, $strType, $strTarget, $bTargetExclusive,
$strTargetAction, $strTargetTimeline, $oRecoveryHashRef, $strComment, $iExpectedExitStatus);
$oHostDbMaster->clusterStop();
# Expect failure because db path is not empty
$strComment = 'path not empty';
$iExpectedExitStatus = ERROR_RESTORE_PATH_NOT_EMPTY;
$oHostDbMaster->restore(
OPTION_DEFAULT_RESTORE_SET, undef, undef, $bDelta, $bForce, $strType, $strTarget, $bTargetExclusive,
$strTargetAction, $strTargetTimeline, $oRecoveryHashRef, $strComment, $iExpectedExitStatus);
# Drop and recreate db path
testPathRemove($oHostDbMaster->dbBasePath());
filePathCreate($oHostDbMaster->dbBasePath());
testPathRemove($oHostDbMaster->dbPath() . '/pg_xlog');
filePathCreate($oHostDbMaster->dbPath() . '/pg_xlog');
testPathRemove($oHostDbMaster->tablespacePath(1));
filePathCreate($oHostDbMaster->tablespacePath(1));
# Now the restore should work
$strComment = undef;
$iExpectedExitStatus = undef;
$oHostDbMaster->restore(
OPTION_DEFAULT_RESTORE_SET, undef, undef, $bDelta, $bForce, $strType, $strTarget, $bTargetExclusive,
$strTargetAction, $strTargetTimeline, $oRecoveryHashRef, $strComment, $iExpectedExitStatus, ' --db-include=test1');
$oHostDbMaster->clusterStart();
$oHostDbMaster->sqlSelectOneTest('select message from test', $strNameMessage);
# Now it should be OK to drop database test2
$oHostDbMaster->sqlExecute('drop database test2', {bAutoCommit => true});
# The test table lives in ts1 so it needs to be moved or dropped
if ($oHostDbMaster->dbVersion() >= PG_VERSION_90)
{
$oHostDbMaster->sqlExecute('alter table test set tablespace pg_default');
}
# Drop for older versions
else
{
$oHostDbMaster->sqlExecute('drop table test');
}
# And drop the tablespace
$oHostDbMaster->sqlExecute("drop tablespace ts1", {bAutoCommit => true});
# Restore (restore type = immediate, inclusive)
#-----------------------------------------------------------------------------------------------------------------------
if ($oHostDbMaster->dbVersion() >= PG_VERSION_94)
{
$bDelta = false;
$bForce = true;
$strType = RECOVERY_TYPE_IMMEDIATE;
$strTarget = undef;
$bTargetExclusive = undef;
$strTargetAction = undef;
$strTargetTimeline = undef;
$oRecoveryHashRef = undef;
$strComment = undef;
$iExpectedExitStatus = undef;
&log(INFO, " testing recovery type = ${strType}");
$oHostDbMaster->clusterStop();
$oHostDbMaster->restore(
$strFullBackup, undef, undef, $bDelta, $bForce, $strType, $strTarget, $bTargetExclusive, $strTargetAction,
$strTargetTimeline, $oRecoveryHashRef, $strComment, $iExpectedExitStatus, undef);
$oHostDbMaster->clusterStart();
$oHostDbMaster->sqlSelectOneTest('select message from test', $strFullMessage);
}
# Restore (restore type = xid, inclusive)
#-----------------------------------------------------------------------------------------------------------------------
$bDelta = false;
$bForce = true;
$strType = RECOVERY_TYPE_XID;
$strTarget = $strXidTarget;
$bTargetExclusive = undef;
$strTargetAction = $oHostDbMaster->dbVersion() >= PG_VERSION_91 ? 'promote' : undef;
$strTargetTimeline = undef;
$oRecoveryHashRef = undef;
$strComment = undef;
$iExpectedExitStatus = undef;
&log(INFO, " testing recovery type = ${strType}");
$oHostDbMaster->clusterStop();
executeTest('rm -rf ' . $oHostDbMaster->dbBasePath() . "/*");
executeTest('rm -rf ' . $oHostDbMaster->dbPath() . "/pg_xlog/*");
$oHostDbMaster->restore(
$strIncrBackup, undef, undef, $bDelta, $bForce, $strType, $strTarget, $bTargetExclusive, $strTargetAction,
$strTargetTimeline, $oRecoveryHashRef, $strComment, $iExpectedExitStatus, '--tablespace-map-all=../../tablespace',
false);
# Save recovery file to test so we can use it in the next test
$oFile->copy(PATH_ABSOLUTE, $oHostDbMaster->dbBasePath() . '/recovery.conf',
PATH_ABSOLUTE, "${strTestPath}/recovery.conf");
$oHostDbMaster->clusterStart();
$oHostDbMaster->sqlSelectOneTest('select message from test', $strXidMessage);
$oHostDbMaster->sqlExecute("update test set message = '$strTimelineMessage'");
# Restore (restore type = preserve, inclusive)
#-----------------------------------------------------------------------------------------------------------------------
$bDelta = false;
$bForce = false;
$strType = RECOVERY_TYPE_PRESERVE;
$strTarget = undef;
$bTargetExclusive = undef;
$strTargetAction = undef;
$strTargetTimeline = undef;
$oRecoveryHashRef = undef;
$strComment = undef;
$iExpectedExitStatus = undef;
&log(INFO, " testing recovery type = ${strType}");
$oHostDbMaster->clusterStop();
executeTest('rm -rf ' . $oHostDbMaster->dbBasePath() . "/*");
executeTest('rm -rf ' . $oHostDbMaster->dbPath() . "/pg_xlog/*");
executeTest('rm -rf ' . $oHostDbMaster->tablespacePath(1) . "/*");
# Restore recovery file that was saved in last test
$oFile->move(PATH_ABSOLUTE, "${strTestPath}/recovery.conf",
PATH_ABSOLUTE, $oHostDbMaster->dbBasePath() . '/recovery.conf');
$oHostDbMaster->restore(
OPTION_DEFAULT_RESTORE_SET, undef, undef, $bDelta, $bForce, $strType, $strTarget, $bTargetExclusive,
$strTargetAction, $strTargetTimeline, $oRecoveryHashRef, $strComment, $iExpectedExitStatus);
$oHostDbMaster->clusterStart();
$oHostDbMaster->sqlSelectOneTest('select message from test', $strXidMessage);
$oHostDbMaster->sqlExecute("update test set message = '$strTimelineMessage'");
# Restore (restore type = time, inclusive) - there is no exclusive time test because I can't find a way to find the
# exact commit time of a transaction.
#-----------------------------------------------------------------------------------------------------------------------
$bDelta = true;
$bForce = false;
$strType = RECOVERY_TYPE_TIME;
$strTarget = $strTimeTarget;
$bTargetExclusive = undef;
$strTargetAction = undef;
$strTargetTimeline = undef;
$oRecoveryHashRef = undef;
$strComment = undef;
$iExpectedExitStatus = undef;
&log(INFO, " testing recovery type = ${strType}");
$oHostDbMaster->clusterStop();
$oHostDbMaster->restore(
$strFullBackup, undef, undef, $bDelta, $bForce, $strType, $strTarget, $bTargetExclusive, $strTargetAction,
$strTargetTimeline, $oRecoveryHashRef, $strComment, $iExpectedExitStatus);
$oHostDbMaster->clusterStart();
$oHostDbMaster->sqlSelectOneTest('select message from test', $strTimeMessage);
# Restore (restore type = xid, exclusive)
#-----------------------------------------------------------------------------------------------------------------------
$bDelta = true;
$bForce = false;
$strType = RECOVERY_TYPE_XID;
$strTarget = $strXidTarget;
$bTargetExclusive = true;
$strTargetAction = undef;
$strTargetTimeline = undef;
$oRecoveryHashRef = undef;
$strComment = undef;
$iExpectedExitStatus = undef;
&log(INFO, " testing recovery type = ${strType}");
$oHostDbMaster->clusterStop();
$oHostDbMaster->restore(
$strIncrBackup, undef, undef, $bDelta, $bForce, $strType, $strTarget, $bTargetExclusive, $strTargetAction,
$strTargetTimeline, $oRecoveryHashRef, $strComment, $iExpectedExitStatus);
$oHostDbMaster->clusterStart();
$oHostDbMaster->sqlSelectOneTest('select message from test', $strIncrMessage);
# Restore (restore type = name)
#-----------------------------------------------------------------------------------------------------------------------
if ($oHostDbMaster->dbVersion() >= PG_VERSION_91)
{
$bDelta = true;
$bForce = true;
$strType = RECOVERY_TYPE_NAME;
$strTarget = $strNameTarget;
$bTargetExclusive = undef;
$strTargetAction = undef;
$strTargetTimeline = undef;
$oRecoveryHashRef = undef;
$strComment = undef;
$iExpectedExitStatus = undef;
&log(INFO, " testing recovery type = ${strType}");
$oHostDbMaster->clusterStop();
$oHostDbMaster->restore(
OPTION_DEFAULT_RESTORE_SET, undef, undef, $bDelta, $bForce, $strType, $strTarget, $bTargetExclusive,
$strTargetAction, $strTargetTimeline, $oRecoveryHashRef, $strComment, $iExpectedExitStatus);
$oHostDbMaster->clusterStart();
$oHostDbMaster->sqlSelectOneTest('select message from test', $strNameMessage);
}
# Restore (restore type = default, timeline = 3)
#-----------------------------------------------------------------------------------------------------------------------
if ($oHostDbMaster->dbVersion() >= PG_VERSION_84)
{
$bDelta = true;
$bForce = false;
$strType = RECOVERY_TYPE_DEFAULT;
$strTarget = undef;
$bTargetExclusive = undef;
$strTargetAction = undef;
$strTargetTimeline = 4;
$oRecoveryHashRef = $oHostDbMaster->dbVersion() >= PG_VERSION_90 ? {'standby-mode' => 'on'} : undef;
$strComment = undef;
$iExpectedExitStatus = undef;
&log(INFO, " testing recovery type = ${strType}");
$oHostDbMaster->clusterStop();
$oHostDbMaster->restore(
$strIncrBackup, undef, undef, $bDelta, $bForce, $strType, $strTarget, $bTargetExclusive, $strTargetAction,
$strTargetTimeline, $oRecoveryHashRef, $strComment, $iExpectedExitStatus);
$oHostDbMaster->clusterStart({bHotStandby => true});
$oHostDbMaster->sqlSelectOneTest('select message from test', $strTimelineMessage, {iTimeout => 120});
}
# Incr backup - make sure a --no-online backup fails
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_INCR;
$oHostBackup->backup(
$strType, 'fail on --no-' . OPTION_ONLINE,
{iExpectedExitStatus => ERROR_POSTMASTER_RUNNING, strOptionalParam => '--no-' . OPTION_ONLINE});
# Incr backup - allow --no-online backup to succeed with --force
#-----------------------------------------------------------------------------------------------------------------------
$strType = BACKUP_TYPE_INCR;
$oHostBackup->backup(
$strType, 'succeed on --no-' . OPTION_ONLINE . ' with --' . OPTION_FORCE,
{strOptionalParam => '--no-' . OPTION_ONLINE . ' --' . OPTION_FORCE});
}
}
}
testCleanup();
}
}
our @EXPORT = qw(backupTestRun);
1;