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/InfoUnitPerlTest.pm
Cynthia Shang e351b8c67c Improve info command to display the stanza cipher type.
Contributed by Cynthia Shang.
Suggested by Douglas J Hunley.
2018-09-10 13:09:45 -04:00

503 lines
26 KiB
Perl

####################################################################################################################################
# Unit tests for Info module
####################################################################################################################################
package pgBackRestTest::Module::Info::InfoUnitPerlTest;
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::Backup::Common;
use pgBackRest::Backup::Info;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Lock;
use pgBackRest::Common::Log;
use pgBackRest::Config::Config;
use pgBackRest::DbVersion;
use pgBackRest::Info;
use pgBackRest::InfoCommon;
use pgBackRest::Manifest;
use pgBackRest::Protocol::Helper;
use pgBackRest::Protocol::Storage::Helper;
use pgBackRest::Storage::Local;
use pgBackRestTest::Common::ExecuteTest;
use pgBackRestTest::Common::RunTest;
use pgBackRestTest::Env::ExpireEnvTest;
use pgBackRestTest::Common::FileTest;
use pgBackRestTest::Env::Host::HostBackupTest;
use pgBackRestTest::Env::HostEnvTest;
####################################################################################################################################
# initModule
####################################################################################################################################
use constant STANZA_ENCRYPT => 'encrypt';
####################################################################################################################################
# initModule
####################################################################################################################################
sub initModule
{
my $self = shift;
$self->{strRepoPath} = $self->testPath() . '/repo';
$self->{strArchivePath} = "$self->{strRepoPath}/archive/" . $self->stanza();
$self->{strBackupPath} = "$self->{strRepoPath}/backup/" . $self->stanza();
$self->{strDbPath} = $self->testPath() . '/db';
$self->{strArchivePathEncrypt} = "$self->{strRepoPath}/archive/" . STANZA_ENCRYPT;
$self->{strBackupPathEncrypt} = "$self->{strRepoPath}/backup/" . STANZA_ENCRYPT;
}
####################################################################################################################################
# initTest
####################################################################################################################################
sub initTest
{
my $self = shift;
# Create parent path for pg_control
storageTest()->pathCreate(($self->{strDbPath} . '/' . DB_PATH_GLOBAL), {bCreateParent => true});
# Create archive info path
storageTest()->pathCreate($self->{strArchivePath}, {bIgnoreExists => true, bCreateParent => true});
# Create backup info path
storageTest()->pathCreate($self->{strBackupPath}, {bIgnoreExists => true, bCreateParent => true});
}
####################################################################################################################################
# initStanzaCreate - initialize options and create the stanza object
####################################################################################################################################
sub initStanzaCreate
{
my $self = shift;
my $strStanza = shift;
my $bEncrypted = shift;
my $strDbVersion = shift;
# Set options for stanzaCreate
my $rhConfig = $self->configTestClear();
if (!defined($strStanza))
{
$strStanza = $self->stanza();
}
if (defined($bEncrypted) && $bEncrypted == true)
{
$self->optionTestSet(CFGOPT_REPO_CIPHER_TYPE, CFGOPTVAL_REPO_CIPHER_TYPE_AES_256_CBC);
$self->optionTestSet(CFGOPT_REPO_CIPHER_PASS, 'x');
}
if (!defined($strDbVersion))
{
$strDbVersion = PG_VERSION_94;
}
$self->optionTestSet(CFGOPT_STANZA, $strStanza);
$self->optionTestSet(CFGOPT_PG_PATH, $self->{strDbPath});
$self->optionTestSet(CFGOPT_REPO_PATH, $self->{strRepoPath});
$self->optionTestSet(CFGOPT_LOG_PATH, $self->testPath());
$self->optionTestSetBool(CFGOPT_ONLINE, false);
$self->optionTestSet(CFGOPT_DB_TIMEOUT, 5);
$self->optionTestSet(CFGOPT_PROTOCOL_TIMEOUT, 6);
$self->configTestLoad(CFGCMD_STANZA_CREATE);
$self->configTestSet($rhConfig);
my $oTestObject = "oExpireTest";
# Create the test object
if (defined($bEncrypted) && $bEncrypted == true)
{
$oTestObject = "oExpireTestEncrypt";
}
$self->{$oTestObject} = new pgBackRestTest::Env::ExpireEnvTest(undef, $self->backrestExe(), storageRepo(), undef, $self);
$self->{$oTestObject}->stanzaCreate($strStanza, $strDbVersion);
}
####################################################################################################################################
# initStanzaUpgrade - initialize options and create the stanza object
####################################################################################################################################
sub initStanzaUpgrade
{
my $self = shift;
# Set options for stanzaCreate
my $rhConfig = $self->configTestClear();
$self->optionTestSet(CFGOPT_STANZA, $self->stanza());
$self->optionTestSet(CFGOPT_PG_PATH, $self->{strDbPath});
$self->optionTestSet(CFGOPT_REPO_PATH, $self->{strRepoPath});
$self->optionTestSet(CFGOPT_LOG_PATH, $self->testPath());
$self->optionTestSetBool(CFGOPT_ONLINE, false);
$self->optionTestSet(CFGOPT_DB_TIMEOUT, 5);
$self->optionTestSet(CFGOPT_PROTOCOL_TIMEOUT, 6);
$self->configTestLoad(CFGCMD_STANZA_UPGRADE);
$self->configTestSet($rhConfig);
# Create the test object
$self->{oExpireTest} = new pgBackRestTest::Env::ExpireEnvTest(undef, $self->backrestExe(), storageRepo(), undef, $self);
$self->{oExpireTest}->stanzaUpgrade($self->stanza(), PG_VERSION_95);
}
####################################################################################################################################
# run
####################################################################################################################################
sub run
{
my $self = shift;
# Used to create backups and WAL to test
use constant SECONDS_PER_DAY => 86400;
my $lBaseTime = 1486137448 - (60 * SECONDS_PER_DAY);
# Create the initial backup "day" number - backup day is incremented for each backup
my $iLastBackup = 5;
################################################################################################################################
if ($self->begin("Info"))
{
$self->configTestLoad(CFGCMD_INFO);
my $oInfo = new pgBackRest::Info();
# Output No stanzas exist in default text option
#---------------------------------------------------------------------------------------------------------------------------
$self->testResult(sub {$oInfo->process()}, undef, 'No stanzas exist and default text option');
# Invalid option
#---------------------------------------------------------------------------------------------------------------------------
cfgOptionSet(CFGOPT_OUTPUT, BOGUS);
$self->testException(sub {$oInfo->process()}, ERROR_ASSERT, "invalid info output option '" . BOGUS . "'");
# Output json option with no stanza defined
#---------------------------------------------------------------------------------------------------------------------------
$self->optionTestSet(CFGOPT_OUTPUT, CFGOPTVAL_INFO_OUTPUT_JSON);
$self->configTestLoad(CFGCMD_INFO);
$self->testResult(sub {$oInfo->process()}, undef, 'json option');
# Add linefeed to JSON
#---------------------------------------------------------------------------------------------------------------------------
my $strJson = '[{"archive" : 1}]';
$self->testResult(sub {$oInfo->outputJSON($strJson)}, 1, 'add linefeed to json');
# Missing stanza path
#---------------------------------------------------------------------------------------------------------------------------
my $hyStanza = $oInfo->stanzaList(BOGUS);
$self->testResult(sub {$oInfo->formatText($hyStanza)},
"stanza: bogus\n status: error (missing stanza path)\n",
'missing stanza path');
# formatBackupText - coverage for certain conditions
#---------------------------------------------------------------------------------------------------------------------------
my $oBackupHash =
{
'archive' =>
{
'start' => 1,
'stop' => undef
},
'timestamp' =>
{
'start' => 1481039848,
'stop' => 1481039848,
},
'label' => 'BACKUPLABEL',
'reference' => ['BACKUPREFERENCE'],
'type' => 'BACKUPTYPE',
};
$self->testResult(sub {$oInfo->formatTextBackup($oBackupHash)},
" BACKUPTYPE backup: BACKUPLABEL\n" .
" timestamp start/stop: 2016-12-06 15:57:28 / 2016-12-06 15:57:28\n" .
" wal start/stop: n/a\n" .
" database size: , backup size: \n" .
" repository size: , repository backup size: \n" .
" backup reference list: BACKUPREFERENCE",
'formatTextBackup');
# Test !isRepoLocal branch
#---------------------------------------------------------------------------------------------------------------------------
cfgOptionSet(CFGOPT_REPO_HOST, false);
cfgOptionSet(CFGOPT_REPO_HOST_CONFIG, BOGUS);
$self->testException(sub {$oInfo->stanzaList(BOGUS)}, ERROR_ASSERT, "option repo1-host-cmd is required");
# dbArchiveSection() -- no archive
#---------------------------------------------------------------------------------------------------------------------------
my $hDbInfo =
{
&INFO_HISTORY_ID => 1,
&INFO_DB_VERSION => PG_VERSION_94,
&INFO_SYSTEM_ID => $self->dbSysId(PG_VERSION_94),
};
$self->testResult(sub {$oInfo->dbArchiveSection($hDbInfo, PG_VERSION_94 . '-1', $self->{strArchivePath}, PG_VERSION_94,
$self->dbSysId(PG_VERSION_94))}, "{database => {id => 1}, id => 9.4-1, max => [undef], min => [undef]}",
'no archive, db-ver match, db-sys match');
$self->testResult(sub {$oInfo->dbArchiveSection($hDbInfo, PG_VERSION_94 . '-1', $self->{strArchivePath}, PG_VERSION_94,
$self->dbSysId(PG_VERSION_95))}, undef, 'no archive, db-ver match, db-sys mismatch');
$hDbInfo->{&INFO_DB_VERSION} = PG_VERSION_95;
$self->testResult(sub {$oInfo->dbArchiveSection($hDbInfo, PG_VERSION_94 . '-1', $self->{strArchivePath}, PG_VERSION_94,
$self->dbSysId(PG_VERSION_94))}, undef, 'no archive, db-ver mismatch, db-sys match');
$hDbInfo->{&INFO_SYSTEM_ID} = $self->dbSysId(PG_VERSION_95);
$self->testResult(sub {$oInfo->dbArchiveSection($hDbInfo, PG_VERSION_94 . '-1', $self->{strArchivePath}, PG_VERSION_94,
$self->dbSysId(PG_VERSION_94))}, undef, 'no archive, db-ver mismatch, db-sys mismatch');
# Create more than one stanza but no data
#---------------------------------------------------------------------------------------------------------------------------
$self->optionTestSet(CFGOPT_STANZA, $self->stanza());
$self->optionTestSet(CFGOPT_REPO_PATH, $self->{strRepoPath});
$self->configTestLoad(CFGCMD_INFO);
# Create archive and backup paths but no info files
storageTest()->pathCreate("$self->{strRepoPath}/archive/" . BOGUS, {bIgnoreExists => true, bCreateParent => true});
storageTest()->pathCreate("$self->{strRepoPath}/backup/" . BOGUS, {bIgnoreExists => true, bCreateParent => true});
# Get a list of all stanzas in the repo
$hyStanza = $oInfo->stanzaList();
$self->testResult(sub {$oInfo->formatText($hyStanza)},
"stanza: bogus\n status: error (missing stanza data)\n cipher: none\n\n" .
"stanza: db\n status: error (missing stanza data)\n cipher: none\n",
'fomatText() multiple stanzas missing data');
# Define the stanza option
#---------------------------------------------------------------------------------------------------------------------------
$self->optionTestSet(CFGOPT_STANZA, $self->stanza());
$self->optionTestClear(CFGOPT_OUTPUT);
$self->configTestLoad(CFGCMD_INFO);
$self->testResult(sub {$oInfo->process()}, undef, 'stanza set');
# Create the stanza - no WAL or backups
#---------------------------------------------------------------------------------------------------------------------------
$self->initStanzaCreate();
$self->configTestLoad(CFGCMD_INFO);
$self->testResult(sub {$oInfo->formatText($oInfo->stanzaList())},
"stanza: bogus\n status: error (missing stanza data)\n cipher: none\n\n" .
"stanza: db\n status: error (no valid backups)\n cipher: none\n\n" .
" db (current)\n wal archive min/max (9.4-1): none present\n",
"formatText() multiple stanzas, one missing data");
$hyStanza = $oInfo->stanzaList($self->stanza());
$self->testResult(sub {$oInfo->formatText($hyStanza)},
"stanza: db\n status: error (no valid backups)\n cipher: none\n\n" .
" db (current)\n wal archive min/max (9.4-1): none present\n",
"formatText() one stanza");
# Create a backup and list backup for just one stanza
#---------------------------------------------------------------------------------------------------------------------------
$self->{oExpireTest}->backupCreate($self->stanza(), CFGOPTVAL_BACKUP_TYPE_FULL, $lBaseTime += SECONDS_PER_DAY, -1, -1);
$hyStanza = $oInfo->stanzaList($self->stanza());
$self->testResult(sub {$oInfo->formatText($hyStanza)},
"stanza: db\n status: ok\n cipher: none\n\n db (current)\n wal archive min/max (9.4-1): none present\n\n" .
" full backup: 20161206-155728F\n" .
" timestamp start/stop: 2016-12-06 15:57:28 / 2016-12-06 15:57:28\n" .
" wal start/stop: n/a\n database size: 0B, backup size: 0B\n" .
" repository size: 0B, repository backup size: 0B\n",
"formatText() one stanza");
# Coverage for major WAL paths with no WAL
#---------------------------------------------------------------------------------------------------------------------------
storageTest()->pathCreate($self->{strArchivePath} . "/9.4-1/0000000100000000", {bIgnoreExists => true});
storageTest()->pathCreate($self->{strArchivePath} . "/9.4-1/0000000200000000", {bIgnoreExists => true});
$hyStanza = $oInfo->stanzaList($self->stanza());
$self->testResult(sub {$oInfo->formatText($hyStanza)},
"stanza: db\n status: ok\n cipher: none\n\n db (current)\n wal archive min/max (9.4-1): none present\n\n" .
" full backup: 20161206-155728F\n" .
" timestamp start/stop: 2016-12-06 15:57:28 / 2016-12-06 15:57:28\n" .
" wal start/stop: n/a\n database size: 0B, backup size: 0B\n" .
" repository size: 0B, repository backup size: 0B\n",
"formatText() major WAL paths with no WAL");
# Upgrade postgres version and backup with WAL
#---------------------------------------------------------------------------------------------------------------------------
undef($self->{oExpireTest});
$self->initStanzaUpgrade();
$self->configTestLoad(CFGCMD_INFO);
$self->{oExpireTest}->backupCreate($self->stanza(), CFGOPTVAL_BACKUP_TYPE_FULL, $lBaseTime += SECONDS_PER_DAY, 1, 1);
$self->{oExpireTest}->backupCreate($self->stanza(), CFGOPTVAL_BACKUP_TYPE_DIFF, $lBaseTime += SECONDS_PER_DAY, 1, 1);
# Remove the 9.4-1 path for condition test coverage
storageTest()->remove($self->{strArchivePath} . "/9.4-1/", {bRecurse => true});
$hyStanza = $oInfo->stanzaList($self->stanza());
$self->testResult(sub {$oInfo->formatText($hyStanza)},
"stanza: db\n status: ok\n cipher: none\n" .
"\n db (current)\n" .
" wal archive min/max (9.5-2): 000000010000000000000000 / 000000010000000000000003\n\n" .
" full backup: 20161207-155728F\n" .
" timestamp start/stop: 2016-12-07 15:57:28 / 2016-12-07 15:57:28\n" .
" wal start/stop: 000000010000000000000000 / 000000010000000000000000\n" .
" database size: 0B, backup size: 0B\n" .
" repository size: 0B, repository backup size: 0B\n\n" .
" diff backup: 20161207-155728F_20161208-155728D\n" .
" timestamp start/stop: 2016-12-08 15:57:28 / 2016-12-08 15:57:28\n" .
" wal start/stop: 000000010000000000000002 / 000000010000000000000002\n" .
" database size: 0B, backup size: 0B\n" .
" repository size: 0B, repository backup size: 0B\n" .
"\n db (prior)\n" .
" full backup: 20161206-155728F\n" .
" timestamp start/stop: 2016-12-06 15:57:28 / 2016-12-06 15:57:28\n" .
" wal start/stop: n/a\n" .
" database size: 0B, backup size: 0B\n" .
" repository size: 0B, repository backup size: 0B\n",
"formatText() multiple DB versions");
# Remove backup from db (prior)
#---------------------------------------------------------------------------------------------------------------------------
# Load the backup.info file
my $oBackupInfo = new pgBackRest::Backup::Info($self->{strBackupPath});
my @stryPath = $oBackupInfo->list(backupRegExpGet(true));
# Remove the db prior full backup from the info file and save it
$oBackupInfo->delete($stryPath[0]);
$oBackupInfo->save();
# Remove the backup directory
storageTest()->remove($self->{strBackupPath} . "/" . $stryPath[0], {bRecurse => true});
$hyStanza = $oInfo->stanzaList($self->stanza());
$self->testResult(sub {$oInfo->formatText($hyStanza)},
"stanza: db\n status: ok\n cipher: none\n" .
"\n db (current)\n" .
" wal archive min/max (9.5-2): 000000010000000000000000 / 000000010000000000000003\n\n" .
" full backup: 20161207-155728F\n" .
" timestamp start/stop: 2016-12-07 15:57:28 / 2016-12-07 15:57:28\n" .
" wal start/stop: 000000010000000000000000 / 000000010000000000000000\n" .
" database size: 0B, backup size: 0B\n" .
" repository size: 0B, repository backup size: 0B\n\n" .
" diff backup: 20161207-155728F_20161208-155728D\n" .
" timestamp start/stop: 2016-12-08 15:57:28 / 2016-12-08 15:57:28\n" .
" wal start/stop: 000000010000000000000002 / 000000010000000000000002\n" .
" database size: 0B, backup size: 0B\n" .
" repository size: 0B, repository backup size: 0B\n",
"db (prior) removed");
# dbArchiveSection() -- with archive
#---------------------------------------------------------------------------------------------------------------------------
$hDbInfo->{&INFO_HISTORY_ID} = 2;
$hDbInfo->{&INFO_DB_VERSION} = PG_VERSION_95;
$hDbInfo->{&INFO_SYSTEM_ID} = $self->dbSysId(PG_VERSION_95);
my $strArchiveId = PG_VERSION_95 . '-2';
$self->testResult(sub {$oInfo->dbArchiveSection($hDbInfo, $strArchiveId, "$self->{strArchivePath}/$strArchiveId",
PG_VERSION_95, $self->dbSysId(PG_VERSION_95))},
"{database => {id => 2}, id => 9.5-2, max => 000000010000000000000003, min => 000000010000000000000000}",
'archive, db-ver match, db-sys-id match');
$self->testResult(sub {$oInfo->dbArchiveSection($hDbInfo, $strArchiveId, "$self->{strArchivePath}/$strArchiveId",
PG_VERSION_95, $self->dbSysId(PG_VERSION_94))},
"{database => {id => 2}, id => 9.5-2, max => 000000010000000000000003, min => 000000010000000000000000}",
'archive, db-ver match, db-sys-id mismatch');
$self->testResult(sub {$oInfo->dbArchiveSection($hDbInfo, $strArchiveId, "$self->{strArchivePath}/$strArchiveId",
PG_VERSION_94, $self->dbSysId(PG_VERSION_95))},
"{database => {id => 2}, id => 9.5-2, max => 000000010000000000000003, min => 000000010000000000000000}",
'archive, db-ver mismatch, db-sys-id match');
$self->testResult(sub {$oInfo->dbArchiveSection($hDbInfo, $strArchiveId, "$self->{strArchivePath}/$strArchiveId",
PG_VERSION_94, $self->dbSysId(PG_VERSION_94))},
"{database => {id => 2}, id => 9.5-2, max => 000000010000000000000003, min => 000000010000000000000000}",
'archive, db-ver mismatch, db-sys-id mismatch');
# Set last backup run in this test set
$iLastBackup = 8;
}
################################################################################################################################
if ($self->begin("Info - encryption"))
{
# create an unencrypted and an encrypted repo and run a backup in each
#--------------------------------------------------------------------------------------------------------------------------
$self->initStanzaCreate();
$self->{oExpireTest}->backupCreate($self->stanza(), CFGOPTVAL_BACKUP_TYPE_FULL, $lBaseTime += SECONDS_PER_DAY, -1, -1);
$iLastBackup++;
my $strUnencryptBackup = ($iLastBackup < 10) ? "0$iLastBackup" : $iLastBackup;
$self->initStanzaCreate(STANZA_ENCRYPT, true);
$self->{oExpireTestEncrypt}->backupCreate(
STANZA_ENCRYPT, CFGOPTVAL_BACKUP_TYPE_FULL, $lBaseTime += SECONDS_PER_DAY, -1, -1);
$iLastBackup++;
my $strEncryptBackup = ($iLastBackup < 10) ? "0$iLastBackup" : $iLastBackup;;
# Clear the main storage repo settings
storageRepoCacheClear(STORAGE_REPO);
# Clear the stanza setting and set the main storage repo basePath to the testing repo path
$self->optionTestClear(CFGOPT_STANZA);
$self->optionTestSet(CFGOPT_REPO_PATH, $self->{strRepoPath});
# Recreate the main storage repo with default unencrypted repo setting and test basePath
storageRepo();
# Test the info command without option --stanza being configured
#--------------------------------------------------------------------------------------------------------------------------
$self->configTestLoad(CFGCMD_INFO);
my $oInfo = new pgBackRest::Info();
$self->testException(sub {$oInfo->stanzaList()}, ERROR_CIPHER,
"unable to parse '" . $self->{strBackupPathEncrypt} . "/backup.info'" .
"\nHINT: Is or was the repo encrypted?" .
"\nHINT: use option --stanza if encryption settings are different for the stanza than the global settings");
# Test the info command with option --stanza configured
#--------------------------------------------------------------------------------------------------------------------------
$self->optionTestSet(CFGOPT_STANZA, $self->stanza());
$self->configTestLoad(CFGCMD_INFO);
$self->testResult(sub {$oInfo->formatText($oInfo->stanzaList($self->stanza()))},
"stanza: db\n status: ok\n cipher: none\n\n db (current)\n wal archive min/max (9.4-1): none present\n\n" .
" full backup: 201612" . $strUnencryptBackup . "-155728F\n" .
" timestamp start/stop: 2016-12-" . $strUnencryptBackup . " 15:57:28 / 2016-12-" . $strUnencryptBackup .
" 15:57:28\n" .
" wal start/stop: n/a\n database size: 0B, backup size: 0B\n" .
" repository size: 0B, repository backup size: 0B\n",
"formatText() unencrypted stanza");
$self->optionTestSet(CFGOPT_STANZA, STANZA_ENCRYPT);
$self->configTestLoad(CFGCMD_INFO);
$self->testResult(sub {$oInfo->formatText($oInfo->stanzaList(STANZA_ENCRYPT))},
"stanza: " . STANZA_ENCRYPT ."\n status: ok\n cipher: aes-256-cbc\n\n db (current)" .
"\n wal archive min/max (9.4-1): none present\n\n" .
" full backup: 201612" . $strEncryptBackup . "-155728F\n" .
" timestamp start/stop: 2016-12-" . $strEncryptBackup . " 15:57:28 / 2016-12-" . $strEncryptBackup .
" 15:57:28\n" .
" wal start/stop: n/a\n database size: 0B, backup size: 0B\n" .
" repository size: 0B, repository backup size: 0B\n",
"formatText() encrypted stanza");
# Change the permissions of the backup file so it cannot be read and confirm correct error reported
#---------------------------------------------------------------------------------------------------------------------------
forceStorageMode(
storageRepo({strStanza => STANZA_ENCRYPT}), $self->{strBackupPathEncrypt} . "/backup.inf*", '220');
$self->testException(sub {$oInfo->backupList(STANZA_ENCRYPT)}, ERROR_FILE_OPEN,
"unable to open '" . $self->{strBackupPathEncrypt} . "/backup.info': Permission denied");
}
}
1;