mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2024-12-14 10:13:05 +02:00
e5511d0f82
Contributed by Cynthia Shang.
273 lines
10 KiB
Perl
273 lines
10 KiB
Perl
####################################################################################################################################
|
|
# CHECK MODULE
|
|
####################################################################################################################################
|
|
package pgBackRest::Check::Check;
|
|
|
|
use strict;
|
|
use warnings FATAL => qw(all);
|
|
use Carp qw(confess);
|
|
use English '-no_match_vars';
|
|
|
|
use pgBackRest::Archive::Common;
|
|
use pgBackRest::Archive::Get::Get;
|
|
use pgBackRest::Backup::Info;
|
|
use pgBackRest::Common::Exception;
|
|
use pgBackRest::Common::Log;
|
|
use pgBackRest::Common::Wait;
|
|
use pgBackRest::Config::Config;
|
|
use pgBackRest::Db;
|
|
use pgBackRest::Protocol::Helper;
|
|
use pgBackRest::Protocol::Storage::Helper;
|
|
|
|
####################################################################################################################################
|
|
# constructor
|
|
####################################################################################################################################
|
|
sub new
|
|
{
|
|
my $class = shift; # Class name
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
my ($strOperation) = logDebugParam(__PACKAGE__ . '->new');
|
|
|
|
# Create the class hash
|
|
my $self = {};
|
|
bless $self, $class;
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn
|
|
(
|
|
$strOperation,
|
|
{name => 'self', value => $self}
|
|
);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# process
|
|
#
|
|
# Validates the database configuration and checks that the archive logs can be read by backup. This will alert the user to any
|
|
# misconfiguration, particularly of archiving, that would result in the inability of a backup to complete (e.g waiting at the end
|
|
# until it times out because it could not find the WAL file).
|
|
####################################################################################################################################
|
|
sub process
|
|
{
|
|
my $self = shift;
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
my $strOperation = logDebugParam(__PACKAGE__ . '->process');
|
|
|
|
# Initialize the local variables
|
|
my $iArchiveTimeout = cfgOption(CFGOPT_ARCHIVE_TIMEOUT);
|
|
|
|
my $iResult = 0;
|
|
my $strResultMessage = undef;
|
|
|
|
my $strArchiveId = undef;
|
|
my $strArchiveFile = undef;
|
|
my $strWalSegment = undef;
|
|
|
|
# Get the master database object to test to see if the manifest can be built
|
|
my ($oDb) = dbMasterGet();
|
|
|
|
# Get the databse version to pass to the manifest constructor and the system-id in the event of a failure
|
|
my ($strDbVersion, $iControlVersion, $iCatalogVersion, $ullDbSysId) = $oDb->info();
|
|
|
|
# Turn off console logging to control when to display the error
|
|
logLevelSet(undef, OFF);
|
|
|
|
# Loop through all defined databases and attempt to build a manifest
|
|
for (my $iRemoteIdx = 1; $iRemoteIdx <= cfgOptionIndexTotal(CFGOPT_DB_HOST); $iRemoteIdx++)
|
|
{
|
|
# Make sure a db is defined for this index
|
|
if (cfgOptionTest(cfgOptionIdFromIndex(CFGOPT_DB_PATH, $iRemoteIdx)) ||
|
|
cfgOptionTest(cfgOptionIdFromIndex(CFGOPT_DB_HOST, $iRemoteIdx)))
|
|
{
|
|
eval
|
|
{
|
|
# Passing file location dev/null so that the save will fail if it is ever attempted. Pass a miscellaneus value for
|
|
# encryption key since the file will not be saved.
|
|
my $oBackupManifest = new pgBackRest::Manifest("/dev/null/manifest.chk",
|
|
{bLoad => false, strDbVersion => $strDbVersion,
|
|
strCipherPass => 'x',
|
|
strCipherPassSub => 'x'});
|
|
|
|
$oBackupManifest->build(storageDb({iRemoteIdx => $iRemoteIdx}),
|
|
cfgOption(cfgOptionIdFromIndex(CFGOPT_DB_PATH, $iRemoteIdx)), undef, cfgOption(CFGOPT_ONLINE));
|
|
|
|
return true;
|
|
}
|
|
or do
|
|
{
|
|
# Capture error information
|
|
$strResultMessage = "Database: ${strDbVersion} ${ullDbSysId} " . exceptionMessage($EVAL_ERROR) .
|
|
(($iResult != 0) ? "\n[$iResult] : $strResultMessage" : "");
|
|
$iResult = exceptionCode($EVAL_ERROR);
|
|
};
|
|
}
|
|
}
|
|
|
|
# Reset the console logging
|
|
logLevelSet(undef, cfgOption(CFGOPT_LOG_LEVEL_CONSOLE));
|
|
|
|
# If the manifest builds are ok, then proceed with the other checks
|
|
if ($iResult == 0)
|
|
{
|
|
# Reinitialize the database object in order to check the configured replicas. This will throw an error if at least one is not
|
|
# able to be connected to and warnings for any that cannot be properly connected to.
|
|
($oDb) = dbObjectGet();
|
|
|
|
# Validate the database configuration
|
|
$oDb->configValidate();
|
|
|
|
# Turn off console logging to control when to display the error
|
|
logLevelSet(undef, OFF);
|
|
|
|
# Check backup.info - if the archive check fails below (e.g --no-archive-check set) then at least know backup.info succeeded
|
|
eval
|
|
{
|
|
# Check that the backup info file is written and is valid for the current database of the stanza
|
|
$self->backupInfoCheck();
|
|
return true;
|
|
}
|
|
# If there is an unhandled error then confess
|
|
or do
|
|
{
|
|
# Capture error information
|
|
$iResult = exceptionCode($EVAL_ERROR);
|
|
$strResultMessage = exceptionMessage($EVAL_ERROR);
|
|
};
|
|
|
|
# Check archive.info
|
|
if ($iResult == 0)
|
|
{
|
|
eval
|
|
{
|
|
# Check that the archive info file is written and is valid for the current database of the stanza
|
|
($strArchiveId) = new pgBackRest::Archive::Get::Get()->getCheck();
|
|
return true;
|
|
}
|
|
or do
|
|
{
|
|
# Capture error information
|
|
$iResult = exceptionCode($EVAL_ERROR);
|
|
$strResultMessage = exceptionMessage($EVAL_ERROR);
|
|
};
|
|
}
|
|
|
|
# If able to get the archive id then force archiving and check the arrival of the archived WAL file with the time specified
|
|
if ($iResult == 0 && !$oDb->isStandby())
|
|
{
|
|
$strWalSegment = $oDb->walSwitch();
|
|
|
|
eval
|
|
{
|
|
$strArchiveFile = walSegmentFind(storageRepo(), $strArchiveId, $strWalSegment, $iArchiveTimeout);
|
|
return true;
|
|
}
|
|
# If this is a backrest error then capture the code and message else confess
|
|
or do
|
|
{
|
|
# Capture error information
|
|
$iResult = exceptionCode($EVAL_ERROR);
|
|
$strResultMessage = exceptionMessage($EVAL_ERROR);
|
|
};
|
|
}
|
|
|
|
# Reset the console logging
|
|
logLevelSet(undef, cfgOption(CFGOPT_LOG_LEVEL_CONSOLE));
|
|
}
|
|
|
|
# If the archiving was successful and backup.info check did not error in an unexpected way, then indicate success
|
|
# Else, log the error.
|
|
if ($iResult == 0)
|
|
{
|
|
if (!$oDb->isStandby())
|
|
{
|
|
&log(INFO,
|
|
"WAL segment ${strWalSegment} successfully stored in the archive at '" .
|
|
storageRepo()->pathGet(STORAGE_REPO_ARCHIVE . "/$strArchiveId/${strArchiveFile}") . "'");
|
|
}
|
|
else
|
|
{
|
|
&log(INFO, 'switch ' . $oDb->walId() . ' cannot be performed on the standby, all other checks passed successfully');
|
|
}
|
|
}
|
|
else
|
|
{
|
|
# Throw the captured error
|
|
&log(ERROR, $strResultMessage, $iResult);
|
|
|
|
# If a WAL switch was attempted, then alert the user that the WAL that did not reach the archive
|
|
if (defined($strWalSegment) && !defined($strArchiveFile))
|
|
{
|
|
&log(WARN,
|
|
"WAL segment ${strWalSegment} did not reach the archive:" . (defined($strArchiveId) ? $strArchiveId : '') . "\n" .
|
|
"HINT: Check the archive_command to ensure that all options are correct (especially --stanza).\n" .
|
|
"HINT: Check the PostgreSQL server log for errors.");
|
|
}
|
|
}
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn
|
|
(
|
|
$strOperation,
|
|
{name => 'iResult', value => $iResult, trace => true}
|
|
);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# backupInfoCheck
|
|
#
|
|
# Check the backup.info file, if it exists, to confirm the DB version, system-id, control and catalog numbers match the database.
|
|
####################################################################################################################################
|
|
sub backupInfoCheck
|
|
{
|
|
my $self = shift;
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
my
|
|
(
|
|
$strOperation,
|
|
$strDbVersion,
|
|
$iControlVersion,
|
|
$iCatalogVersion,
|
|
$ullDbSysId,
|
|
) =
|
|
logDebugParam
|
|
(
|
|
__PACKAGE__ . '->backupInfoCheck', \@_,
|
|
{name => 'strDbVersion', required => false},
|
|
{name => 'iControlVersion', required => false},
|
|
{name => 'iCatalogVersion', required => false},
|
|
{name => 'ullDbSysId', required => false}
|
|
);
|
|
|
|
# If the db info are not passed, then we need to retrieve the database information
|
|
my $iDbHistoryId;
|
|
|
|
if (!defined($strDbVersion) || !defined($iControlVersion) || !defined($iCatalogVersion) || !defined($ullDbSysId))
|
|
{
|
|
# get DB info for comparison
|
|
($strDbVersion, $iControlVersion, $iCatalogVersion, $ullDbSysId) = dbMasterGet()->info();
|
|
}
|
|
|
|
if (!isRepoLocal())
|
|
{
|
|
$iDbHistoryId = protocolGet(CFGOPTVAL_REMOTE_TYPE_BACKUP)->cmdExecute(
|
|
OP_CHECK_BACKUP_INFO_CHECK, [$strDbVersion, $iControlVersion, $iCatalogVersion, $ullDbSysId]);
|
|
}
|
|
else
|
|
{
|
|
$iDbHistoryId = (new pgBackRest::Backup::Info(storageRepo()->pathGet(STORAGE_REPO_BACKUP)))->check(
|
|
$strDbVersion, $iControlVersion, $iCatalogVersion, $ullDbSysId);
|
|
}
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn
|
|
(
|
|
$strOperation,
|
|
{name => 'iDbHistoryId', value => $iDbHistoryId, trace => true}
|
|
);
|
|
}
|
|
|
|
1;
|