1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-14 10:13:05 +02:00
pgbackrest/test/lib/pgBackRestTest/Module/Info/InfoInfoArchivePerlTest.pm

250 lines
13 KiB
Perl
Raw Normal View History

####################################################################################################################################
# Unit tests for ArchiveInfo
####################################################################################################################################
package pgBackRestTest::Module::Info::InfoInfoArchivePerlTest;
use parent 'pgBackRestTest::Env::HostEnvTest';
####################################################################################################################################
# Perl includes
####################################################################################################################################
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use File::Basename qw(dirname);
use Storable qw(dclone);
use pgBackRest::Archive::Info;
use pgBackRest::Backup::Info;
use pgBackRest::Common::Cipher;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Ini;
use pgBackRest::Common::Lock;
use pgBackRest::Common::Log;
use pgBackRest::Config::Config;
use pgBackRest::DbVersion;
use pgBackRest::InfoCommon;
use pgBackRest::Manifest;
use pgBackRest::Protocol::Storage::Helper;
use pgBackRest::Storage::Base;
use pgBackRestTest::Env::HostEnvTest;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
####################################################################################################################################
# initModule
####################################################################################################################################
sub initModule
{
my $self = shift;
$self->{strRepoPath} = $self->testPath() . '/repo';
}
####################################################################################################################################
# initTest
####################################################################################################################################
sub initTest
{
my $self = shift;
# Clear cache from the previous test
storageRepoCacheClear();
# Load options
$self->configTestClear();
$self->optionTestSet(CFGOPT_STANZA, $self->stanza());
$self->optionTestSet(CFGOPT_REPO_PATH, $self->testPath() . '/repo');
$self->configTestLoad(CFGCMD_ARCHIVE_PUSH);
# Create backup info path
storageRepo()->pathCreate(STORAGE_REPO_BACKUP, {bCreateParent => true});
# Create archive info path
storageRepo()->pathCreate(STORAGE_REPO_ARCHIVE, {bCreateParent => true});
}
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
my $strArchiveTestFile = $self->dataPath() . '/backup.wal1_';
################################################################################################################################
if ($self->begin("Archive::Info::new()"))
{
$self->testException(sub {new pgBackRest::Archive::Info(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE))}, ERROR_FILE_MISSING,
ARCHIVE_INFO_FILE . " does not exist but is required to push/get WAL segments\n" .
"HINT: is archive_command configured in postgresql.conf?\n" .
"HINT: has a stanza-create been performed?\n" .
"HINT: use --no-archive-check to disable archive checks during backup if you have an alternate archiving scheme.");
}
################################################################################################################################
if ($self->begin("Archive::Info::reconstruct()"))
{
my $tWalContent = $self->walGenerateContent(PG_VERSION_94);
my $strWalChecksum = $self->walGenerateContentChecksum(PG_VERSION_94);
my $oArchiveInfo = new pgBackRest::Archive::Info(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE), false,
{bLoad => false, bIgnoreMissing => true});
storageRepo()->pathCreate(STORAGE_REPO_ARCHIVE . "/" . PG_VERSION_94 . "-1/0000000100000001", {bCreateParent => true});
my $strArchiveFile = storageRepo()->pathGet(STORAGE_REPO_ARCHIVE . "/" . PG_VERSION_94 . "-1/0000000100000001/") .
"000000010000000100000001-${strWalChecksum}";
storageTest()->put($strArchiveFile, $tWalContent);
$self->testResult(sub {$oArchiveInfo->reconstruct(PG_VERSION_94, $self->dbSysId(PG_VERSION_94))}, "[undef]", 'reconstruct');
$self->testResult(sub {$oArchiveInfo->check(PG_VERSION_94, $self->dbSysId(PG_VERSION_94), false)}, PG_VERSION_94 . "-1",
' check reconstruct');
# Attempt to reconstruct from an encypted archived WAL for an unencrypted repo
#---------------------------------------------------------------------------------------------------------------------------
# Prepend encryption Magic signature to simulate encryption
executeTest('echo "' . CIPHER_MAGIC . '" > ' . $strArchiveFile);
$self->testException(sub {$oArchiveInfo->reconstruct(PG_VERSION_94, $self->dbSysId(PG_VERSION_94))}, ERROR_CRYPTO,
"encryption incompatible for '$strArchiveFile'" .
"\nHINT: is or was the repo encrypted?");
executeTest('rm ' . $strArchiveFile);
# Attempt to reconstruct from an encypted archived WAL with an encrypted repo
#---------------------------------------------------------------------------------------------------------------------------
storageRepoCacheClear();
$self->optionTestSet(CFGOPT_REPO_CIPHER_TYPE, CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC);
$self->optionTestSet(CFGOPT_REPO_CIPHER_PASS, 'x');
$self->configTestLoad(CFGCMD_ARCHIVE_PUSH);
# Instantiate an archive.info object with a sub passphrase for the archived WAL
my $strCipherPassSub = 'y';
$oArchiveInfo = new pgBackRest::Archive::Info(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE), false,
{bLoad => false, bIgnoreMissing => true, strCipherPassSub => $strCipherPassSub});
# Create an encrypted archived WAL
storageRepo()->put($strArchiveFile, $tWalContent, {strCipherPass => $strCipherPassSub});
$self->testResult(sub {$oArchiveInfo->reconstruct(PG_VERSION_94, $self->dbSysId(PG_VERSION_94))}, "[undef]", 'reconstruct');
$self->testResult(sub {$oArchiveInfo->check(PG_VERSION_94, $self->dbSysId(PG_VERSION_94), false)}, PG_VERSION_94 . "-1",
' check reconstruction from encrypted archive');
$oArchiveInfo->save();
# Confirm encrypted
$self->testResult(sub {storageRepo()->encrypted(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE) . '/'
. ARCHIVE_INFO_FILE) && ($oArchiveInfo->cipherPassSub() eq $strCipherPassSub)}, true,
' new archive info encrypted');
}
################################################################################################################################
if ($self->begin("Archive::Info::archiveIdList(), check(), archiveId()"))
{
my @stryArchiveId;
my $oArchiveInfo = new pgBackRest::Archive::Info(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE), false,
{bLoad => false, bIgnoreMissing => true});
$oArchiveInfo->create(PG_VERSION_92, $self->dbSysId(PG_VERSION_92), false);
$oArchiveInfo->dbSectionSet(PG_VERSION_93, $self->dbSysId(PG_VERSION_93), $oArchiveInfo->dbHistoryIdGet(false) + 1);
$oArchiveInfo->dbSectionSet(PG_VERSION_94, $self->dbSysId(PG_VERSION_94), $oArchiveInfo->dbHistoryIdGet(false) + 10);
$oArchiveInfo->dbSectionSet(PG_VERSION_93, $self->dbSysId(PG_VERSION_93), $oArchiveInfo->dbHistoryIdGet(false) + 1);
$oArchiveInfo->save();
# Check gets only the latest DB and returns only that archiveId
push(@stryArchiveId, $oArchiveInfo->check(PG_VERSION_93, $self->dbSysId(PG_VERSION_93)));
$self->testResult(sub {(@stryArchiveId == 1) && ($stryArchiveId[0] eq PG_VERSION_93 . "-13")}, true,
'check - return only newest archiveId');
$self->testResult(sub {$oArchiveInfo->archiveId(
{strDbVersion => PG_VERSION_93, ullDbSysId => $self->dbSysId(PG_VERSION_93)})},
PG_VERSION_93 . "-13", 'archiveId - return only newest archiveId for multiple histories');
$self->testException(
sub {$oArchiveInfo->archiveId({strDbVersion => PG_VERSION_94, ullDbSysId => BOGUS})}, ERROR_ARCHIVE_MISMATCH,
"unable to retrieve the archive id for database version '" . PG_VERSION_94 . "' and system-id '" . BOGUS . "'");
$self->testException(sub {$oArchiveInfo->check(PG_VERSION_94, $self->dbSysId(PG_VERSION_94))}, ERROR_ARCHIVE_MISMATCH,
"WAL segment version " . PG_VERSION_94 . " does not match archive version " . PG_VERSION_93 .
"\nWAL segment system-id " . $self->dbSysId(PG_VERSION_94) . " does not match archive system-id " .
$self->dbSysId(PG_VERSION_93) . "\nHINT: are you archiving to the correct stanza?");
@stryArchiveId = $oArchiveInfo->archiveIdList(PG_VERSION_93, $self->dbSysId(PG_VERSION_93));
$self->testResult(sub {(@stryArchiveId == 2) && ($stryArchiveId[0] eq PG_VERSION_93 . "-13") &&
($stryArchiveId[1] eq PG_VERSION_93 . "-2")}, true, 'archiveIdList - returns multiple archiveId - newest first');
@stryArchiveId = $oArchiveInfo->archiveIdList(PG_VERSION_94, $self->dbSysId(PG_VERSION_94));
$self->testResult(sub {(@stryArchiveId == 1) && ($stryArchiveId[0] eq PG_VERSION_94 . "-12")}, true,
'archiveIdList - returns older archiveId');
$self->testException(
sub {$oArchiveInfo->archiveIdList(PG_VERSION_95, $self->dbSysId(PG_VERSION_94))}, ERROR_ARCHIVE_MISMATCH,
"unable to retrieve the archive id for database version '" . PG_VERSION_95 . "' and system-id '" .
$self->dbSysId(PG_VERSION_94) . "'");
}
################################################################################################################################
if ($self->begin("encryption"))
{
# Create an unencrypted archive.info file
#---------------------------------------------------------------------------------------------------------------------------
my $oArchiveInfo = new pgBackRest::Archive::Info(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE), false,
{bLoad => false, bIgnoreMissing => true});
$oArchiveInfo->create(PG_VERSION_94, $self->dbSysId(PG_VERSION_94), true);
# Confirm unencrypted
$self->testResult(sub {storageRepo()->encrypted(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE) . '/'
. ARCHIVE_INFO_FILE)}, false, ' new archive info unencrypted');
my $strFile = $oArchiveInfo->{strFileName};
# Prepend encryption Magic signature to simulate encryption
executeTest('echo "' . CIPHER_MAGIC . '$(cat ' . $strFile . ')" > ' . $strFile);
$self->testException(sub {new pgBackRest::Archive::Info(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE))}, ERROR_CRYPTO,
"unable to parse '$strFile'" .
"\nHINT: is or was the repo encrypted?");
# Remove the archive info files
executeTest('rm ' . $oArchiveInfo->{strFileName} . '*');
# Create an encrypted storage and archive.info file
#---------------------------------------------------------------------------------------------------------------------------
my $strCipherPass = 'x';
$self->configTestClear();
$self->optionTestSet(CFGOPT_REPO_CIPHER_TYPE, CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC);
$self->optionTestSet(CFGOPT_REPO_CIPHER_PASS, $strCipherPass);
$self->optionTestSet(CFGOPT_STANZA, $self->stanza());
$self->optionTestSet(CFGOPT_REPO_PATH, $self->testPath() . '/repo');
$self->configTestLoad(CFGCMD_ARCHIVE_PUSH);
# Clear the storage repo settings
storageRepoCacheClear();
# Create an encrypted storage and generate an encryption sub passphrase to store in the file
my $strCipherPassSub = cipherPassGen();
# Error on encrypted repo but no passphrase passed to store in the file
$self->testException(sub {new pgBackRest::Archive::Info(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE), false,
{bLoad => false, bIgnoreMissing => true})}, ERROR_ASSERT,
'a user passphrase and sub passphrase are both required when encrypting');
# Create an encrypted archiveInfo file
$oArchiveInfo = new pgBackRest::Archive::Info(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE), false,
{bLoad => false, bIgnoreMissing => true, strCipherPassSub => $strCipherPassSub});
$oArchiveInfo->create(PG_VERSION_94, $self->dbSysId(PG_VERSION_94), true);
# Confirm encrypted
$self->testResult(sub {storageRepo()->encrypted(storageRepo()->pathGet(STORAGE_REPO_ARCHIVE) . '/'
. ARCHIVE_INFO_FILE)}, true, ' new archive info encrypted');
$self->testResult(sub {$oArchiveInfo->test(INI_SECTION_CIPHER, INI_KEY_CIPHER_PASS, undef, $strCipherPassSub)},
true, ' generated passphrase stored');
}
}
1;