1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-14 10:13:05 +02:00
pgbackrest/lib/pgBackRest/Backup/Info.pm
Cynthia Shang bacc08792d Ensure latest db-id is selected on when matching archive.info to backup.info.
This provides correct matching in the event there are system-id and db-version duplicates (e.g. after reverting a pg_upgrade).

Fixed by Cynthia Shang.
Reported by Adam K. Sumner.
2018-01-30 16:49:47 -05:00

1062 lines
41 KiB
Perl

####################################################################################################################################
# BACKUP INFO MODULE
####################################################################################################################################
package pgBackRest::Backup::Info;
use parent 'pgBackRest::Common::Ini';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use English '-no_match_vars';
use Exporter qw(import);
our @EXPORT = qw();
use File::Basename qw(dirname basename);
use File::stat;
use pgBackRest::Archive::Info;
use pgBackRest::Backup::Common;
use pgBackRest::Common::Exception;
use pgBackRest::Common::Ini;
use pgBackRest::Common::Log;
use pgBackRest::Config::Config;
use pgBackRest::InfoCommon;
use pgBackRest::Manifest;
use pgBackRest::Protocol::Helper;
use pgBackRest::Protocol::Storage::Helper;
use pgBackRest::Storage::Helper;
####################################################################################################################################
# File/path constants
####################################################################################################################################
use constant FILE_BACKUP_INFO => 'backup.info';
push @EXPORT, qw(FILE_BACKUP_INFO);
####################################################################################################################################
# Backup info constants
####################################################################################################################################
use constant INFO_BACKUP_SECTION_BACKUP => MANIFEST_SECTION_BACKUP;
push @EXPORT, qw(INFO_BACKUP_SECTION_BACKUP);
use constant INFO_BACKUP_SECTION_BACKUP_CURRENT => INFO_BACKUP_SECTION_BACKUP . ':current';
push @EXPORT, qw(INFO_BACKUP_SECTION_BACKUP_CURRENT);
use constant INFO_BACKUP_KEY_ARCHIVE_CHECK => MANIFEST_KEY_ARCHIVE_CHECK;
push @EXPORT, qw(INFO_BACKUP_KEY_ARCHIVE_CHECK);
use constant INFO_BACKUP_KEY_ARCHIVE_COPY => MANIFEST_KEY_ARCHIVE_COPY;
push @EXPORT, qw(INFO_BACKUP_KEY_ARCHIVE_COPY);
use constant INFO_BACKUP_KEY_ARCHIVE_START => MANIFEST_KEY_ARCHIVE_START;
push @EXPORT, qw(INFO_BACKUP_KEY_ARCHIVE_START);
use constant INFO_BACKUP_KEY_ARCHIVE_STOP => MANIFEST_KEY_ARCHIVE_STOP;
push @EXPORT, qw(INFO_BACKUP_KEY_ARCHIVE_STOP);
use constant INFO_BACKUP_KEY_BACKUP_STANDBY => MANIFEST_KEY_BACKUP_STANDBY;
push @EXPORT, qw(INFO_BACKUP_KEY_BACKUP_STANDBY);
use constant INFO_BACKUP_KEY_BACKUP_REPO_SIZE => 'backup-info-repo-size';
push @EXPORT, qw(INFO_BACKUP_KEY_BACKUP_REPO_SIZE);
use constant INFO_BACKUP_KEY_BACKUP_REPO_SIZE_DELTA => 'backup-info-repo-size-delta';
push @EXPORT, qw(INFO_BACKUP_KEY_BACKUP_REPO_SIZE_DELTA);
use constant INFO_BACKUP_KEY_BACKUP_SIZE => 'backup-info-size';
push @EXPORT, qw(INFO_BACKUP_KEY_BACKUP_SIZE);
use constant INFO_BACKUP_KEY_BACKUP_SIZE_DELTA => 'backup-info-size-delta';
push @EXPORT, qw(INFO_BACKUP_KEY_BACKUP_SIZE_DELTA);
use constant INFO_BACKUP_KEY_CATALOG => MANIFEST_KEY_CATALOG;
push @EXPORT, qw(INFO_BACKUP_KEY_CATALOG);
use constant INFO_BACKUP_KEY_CONTROL => MANIFEST_KEY_CONTROL;
push @EXPORT, qw(INFO_BACKUP_KEY_CONTROL);
use constant INFO_BACKUP_KEY_COMPRESS => MANIFEST_KEY_COMPRESS;
push @EXPORT, qw(INFO_BACKUP_KEY_COMPRESS);
use constant INFO_BACKUP_KEY_CHECKSUM_PAGE => MANIFEST_KEY_CHECKSUM_PAGE;
push @EXPORT, qw(INFO_BACKUP_KEY_CHECKSUM_PAGE);
use constant INFO_BACKUP_KEY_DB_VERSION => MANIFEST_KEY_DB_VERSION;
push @EXPORT, qw(INFO_BACKUP_KEY_DB_VERSION);
use constant INFO_BACKUP_KEY_FORMAT => INI_KEY_FORMAT;
push @EXPORT, qw(INFO_BACKUP_KEY_FORMAT);
use constant INFO_BACKUP_KEY_HARDLINK => MANIFEST_KEY_HARDLINK;
push @EXPORT, qw(INFO_BACKUP_KEY_HARDLINK);
use constant INFO_BACKUP_KEY_HISTORY_ID => MANIFEST_KEY_DB_ID;
push @EXPORT, qw(INFO_BACKUP_KEY_HISTORY_ID);
use constant INFO_BACKUP_KEY_LABEL => MANIFEST_KEY_LABEL;
push @EXPORT, qw(INFO_BACKUP_KEY_LABEL);
use constant INFO_BACKUP_KEY_PRIOR => MANIFEST_KEY_PRIOR;
push @EXPORT, qw(INFO_BACKUP_KEY_PRIOR);
use constant INFO_BACKUP_KEY_REFERENCE => 'backup-reference';
push @EXPORT, qw(INFO_BACKUP_KEY_REFERENCE);
use constant INFO_BACKUP_KEY_ONLINE => MANIFEST_KEY_ONLINE;
push @EXPORT, qw(INFO_BACKUP_KEY_ONLINE);
use constant INFO_BACKUP_KEY_SYSTEM_ID => MANIFEST_KEY_SYSTEM_ID;
push @EXPORT, qw(INFO_BACKUP_KEY_SYSTEM_ID);
use constant INFO_BACKUP_KEY_TIMESTAMP_START => MANIFEST_KEY_TIMESTAMP_START;
push @EXPORT, qw(INFO_BACKUP_KEY_TIMESTAMP_START);
use constant INFO_BACKUP_KEY_TIMESTAMP_STOP => MANIFEST_KEY_TIMESTAMP_STOP;
push @EXPORT, qw(INFO_BACKUP_KEY_TIMESTAMP_STOP);
use constant INFO_BACKUP_KEY_TYPE => MANIFEST_KEY_TYPE;
push @EXPORT, qw(INFO_BACKUP_KEY_TYPE);
use constant INFO_BACKUP_KEY_VERSION => INI_KEY_VERSION;
push @EXPORT, qw(INFO_BACKUP_KEY_VERSION);
####################################################################################################################################
# Global variables
####################################################################################################################################
my $strBackupInfoMissingMsg =
FILE_BACKUP_INFO . " does not exist and is required to perform a backup.\n" .
"HINT: has a stanza-create been performed?";
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strBackupClusterPath,
$bValidate,
$bRequired,
$oStorage,
$bLoad, # Should the file attemp to be loaded?
$bIgnoreMissing, # Don't error on missing files
$strCipherPassSub, # Generated passphrase to encrypt manifest files if the repo is encrypted
) =
logDebugParam
(
__PACKAGE__ . '->new', \@_,
{name => 'strBackupClusterPath'},
{name => 'bValidate', default => true},
{name => 'bRequired', default => true},
{name => 'oStorage', optional => true, default => storageRepo()},
{name => 'bLoad', optional => true, default => true},
{name => 'bIgnoreMissing', optional => true, default => false},
{name => 'strCipherPassSub', optional => true},
);
# Build the backup info path/file name
my $strBackupInfoFile = "${strBackupClusterPath}/" . FILE_BACKUP_INFO;
my $self = {};
my $iResult = 0;
my $strResultMessage;
# Init object and store variables
eval
{
$self = $class->SUPER::new($strBackupInfoFile, {bLoad => $bLoad, bIgnoreMissing => $bIgnoreMissing,
oStorage => $oStorage, strCipherPass => $oStorage->cipherPassUser(),
strCipherPassSub => $strCipherPassSub});
return true;
}
or do
{
# Capture error information
$iResult = exceptionCode($EVAL_ERROR);
$strResultMessage = exceptionMessage($EVAL_ERROR);
};
if ($iResult != 0)
{
# If the backup info file does not exist and is required, then throw an error
# The backup info is only allowed not to exist when running a stanza-create on a new install
if ($iResult == ERROR_FILE_MISSING)
{
if ($bRequired)
{
confess &log(ERROR, "${strBackupClusterPath}/$strBackupInfoMissingMsg", ERROR_FILE_MISSING);
}
}
elsif ($iResult == ERROR_CIPHER && $strResultMessage =~ "^unable to flush")
{
confess &log(ERROR, "unable to parse '$strBackupInfoFile'\nHINT: Is or was the repo encrypted?", $iResult);
}
else
{
confess $EVAL_ERROR;
}
}
$self->{strBackupClusterPath} = $strBackupClusterPath;
$self->{oStorage} = $oStorage;
# Validate the backup info
if ($bValidate)
{
$self->validate();
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'self', value => $self}
);
}
####################################################################################################################################
# validate
#
# Comfirm the file exists and reconstruct as necessary.
####################################################################################################################################
sub validate
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my ($strOperation) = logDebugParam(__PACKAGE__ . '->validate');
# Confirm the info file exists with the DB section
$self->confirmExists();
$self->reconstruct();
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# reconstruct
#
# Compare the backup info against the actual backups in the repository. Reconstruct the file based on manifests missing or no
# longer valid.
####################################################################################################################################
sub reconstruct
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$bSave,
$bRequired, # If false then must be creating or reconstructing so the DB info must be supplied
$strDbVersion,
$ullDbSysId,
$iControlVersion,
$iCatalogVersion,
) =
logDebugParam
(
__PACKAGE__ . '->reconstruct', \@_,
{name => 'bSave', default => true},
{name => 'bRequired', default => true},
{name => 'strDbVersion', required => false},
{name => 'ullDbSysId', required => false},
{name => 'iControlVersion', required => false},
{name => 'iCatalogVersion', required => false},
);
# Check for backups that are not in FILE_BACKUP_INFO
foreach my $strBackup ($self->{oStorage}->list(
$self->{strBackupClusterPath}, {strExpression => backupRegExpGet(true, true, true)}))
{
my $strManifestFile = "$self->{strBackupClusterPath}/${strBackup}/" . FILE_MANIFEST;
# ??? Check for and move history files that were not moved before and maybe don't consider it to be an error when they
# can't be moved. This would also be true for the first move attempt in Backup->process();
if (!$self->current($strBackup) && $self->{oStorage}->exists($strManifestFile))
{
my $oManifest = pgBackRest::Manifest->new($strManifestFile,
{strCipherPass => ($self->{oStorage}->encrypted($strManifestFile)) ? $self->cipherPassSub() : undef});
# If we are reconstructing, then we need to be sure this db-id and version is in the history section. Also if it
# has a db-id greater than anything in the history section, then add it to the db section.
if (!$bRequired)
{
my $hDbList = $self->dbHistoryList();
my $iDbId = $oManifest->get(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_ID);
my $iDbIdMax = 0;
my $ullDbSysId = $oManifest->get(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_SYSTEM_ID);
my $strDbVersion = $oManifest->get(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION);
# If this is the max history id then set the db section
foreach my $iDbHistoryId (keys %{$hDbList})
{
# If the current history ID is greater than the running max, then set it to the current id
if ($iDbHistoryId > $iDbIdMax)
{
$iDbIdMax = $iDbHistoryId;
}
}
if ($iDbId >= $iDbIdMax)
{
$self->dbSectionSet($strDbVersion, $oManifest->get(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_CONTROL),
$oManifest->get(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_CATALOG), $ullDbSysId, $iDbId);
}
}
&log(WARN, "backup ${strBackup} found in repository added to " . FILE_BACKUP_INFO);
$self->add($oManifest, $bSave, $bRequired);
}
}
# If reconstructing, make sure the DB section is correct
if (!$bRequired)
{
# If any database info is missing, then assert
if (!defined($strDbVersion) || !defined($ullDbSysId) || !defined($iControlVersion) || !defined($iCatalogVersion))
{
confess &log(ASSERT, "backup info cannot be reconstructed without database information");
}
# If the DB section does not exist then create the db and history section
elsif (!$self->test(INFO_BACKUP_SECTION_DB))
{
$self->create($strDbVersion, $ullDbSysId, $iControlVersion, $iCatalogVersion, $bSave);
}
# Else update the DB section if it does not match the current database
else
{
# Turn off console logging to control when to display the error
logDisable();
eval
{
$self->check($strDbVersion, $iControlVersion, $iCatalogVersion, $ullDbSysId, $bRequired);
logEnable();
return true;
}
or do
{
# Reset the console logging
logEnable();
# Confess unhandled errors
confess $EVAL_ERROR if (exceptionCode($EVAL_ERROR) != ERROR_BACKUP_MISMATCH);
# Update the DB section if it does not match the current database
$self->dbSectionSet($strDbVersion, $iControlVersion, $iCatalogVersion, $ullDbSysId, $self->dbHistoryIdGet(false)+1);
};
}
}
# Remove backups from FILE_BACKUP_INFO that are no longer in the repository
foreach my $strBackup ($self->keys(INFO_BACKUP_SECTION_BACKUP_CURRENT))
{
my $strManifestFile = "$self->{strBackupClusterPath}/${strBackup}/" . FILE_MANIFEST;
my $strBackupPath = "$self->{strBackupClusterPath}/${strBackup}";
if (!$self->{oStorage}->pathExists($strBackupPath))
{
&log(WARN, "backup ${strBackup} missing in repository removed from " . FILE_BACKUP_INFO);
$self->delete($strBackup);
}
elsif (!$self->{oStorage}->exists($strManifestFile))
{
&log(WARN, "backup ${strBackup} missing manifest removed from " . FILE_BACKUP_INFO);
$self->delete($strBackup);
}
}
# ??? Add a section to remove backups that are missing references (unless they are hardlinked?)
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# check
#
# Check db info and make sure it matches what is already in the repository. Return the db-id if everything matches.
####################################################################################################################################
sub check
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strDbVersion,
$iControlVersion,
$iCatalogVersion,
$ullDbSysId,
$bRequired,
) =
logDebugParam
(
__PACKAGE__ . '->check', \@_,
{name => 'strDbVersion', trace => true},
{name => 'iControlVersion', trace => true},
{name => 'iCatalogVersion', trace => true},
{name => 'ullDbSysId', trace => true},
{name => 'bRequired', default => true},
);
# Confirm the info file exists with the DB section
if ($bRequired)
{
$self->confirmExists();
}
if (!$self->test(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_SYSTEM_ID, undef, $ullDbSysId) ||
!$self->test(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_DB_VERSION, undef, $strDbVersion))
{
confess &log(ERROR, "database version = ${strDbVersion}, system-id ${ullDbSysId} does not match backup version = " .
$self->get(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_DB_VERSION) . ", system-id = " .
$self->get(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_SYSTEM_ID) . "\n" .
"HINT: is this the correct stanza?", ERROR_BACKUP_MISMATCH);
}
if (!$self->test(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_CATALOG, undef, $iCatalogVersion) ||
!$self->test(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_CONTROL, undef, $iControlVersion))
{
confess &log(ERROR, "database control-version = ${iControlVersion}, catalog-version ${iCatalogVersion}" .
" does not match backup control-version = " .
$self->get(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_CONTROL) . ", catalog-version = " .
$self->get(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_CATALOG) . "\n" .
"HINT: this may be a symptom of database or repository corruption!", ERROR_BACKUP_MISMATCH);
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'iDbHistoryId', value => $self->numericGet(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_HISTORY_ID)}
);
}
####################################################################################################################################
# add
#
# Add a backup to the info file.
####################################################################################################################################
sub add
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$oBackupManifest,
$bSave,
$bRequired,
) =
logDebugParam
(
__PACKAGE__ . '->add', \@_,
{name => 'oBackupManifest', trace => true},
{name => 'bSave', default => true, trace => true},
{name => 'bRequired', default => true, trace => true},
);
# Confirm the info file exists with the DB section
if ($bRequired)
{
$self->confirmExists();
}
# Get the backup label
my $strBackupLabel = $oBackupManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LABEL);
# Calculate backup sizes and references
my $lBackupSize = 0;
my $lBackupSizeDelta = 0;
my $lBackupRepoSize = 0;
my $lBackupRepoSizeDelta = 0;
my $oReferenceHash = undef;
foreach my $strFileKey ($oBackupManifest->keys(MANIFEST_SECTION_TARGET_FILE))
{
my $lFileSize =
$oBackupManifest->get(MANIFEST_SECTION_TARGET_FILE, $strFileKey, MANIFEST_SUBKEY_SIZE);
my $lRepoSize =
$oBackupManifest->get(MANIFEST_SECTION_TARGET_FILE, $strFileKey, MANIFEST_SUBKEY_REPO_SIZE, false, $lFileSize);
my $strFileReference =
$oBackupManifest->get(MANIFEST_SECTION_TARGET_FILE, $strFileKey, MANIFEST_SUBKEY_REFERENCE, false);
# Temporary until compressed size is back in
$lBackupSize += $lFileSize;
$lBackupRepoSize += $lRepoSize;
if (defined($strFileReference))
{
$$oReferenceHash{$strFileReference} = true;
}
else
{
$lBackupSizeDelta += $lFileSize;
$lBackupRepoSizeDelta += $lRepoSize;
}
}
# Set backup size info
$self->numericSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_BACKUP_SIZE, $lBackupSize);
$self->numericSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_BACKUP_SIZE_DELTA, $lBackupSizeDelta);
$self->numericSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_BACKUP_REPO_SIZE, $lBackupRepoSize);
$self->numericSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_BACKUP_REPO_SIZE_DELTA,
$lBackupRepoSizeDelta);
$self->boolSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_ARCHIVE_CHECK,
$oBackupManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_ARCHIVE_CHECK));
$self->boolSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_ARCHIVE_COPY,
$oBackupManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_ARCHIVE_COPY));
$self->set(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_ARCHIVE_START,
$oBackupManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_START, undef, false));
$self->set(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_ARCHIVE_STOP,
$oBackupManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_ARCHIVE_STOP, undef, false));
$self->boolSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_BACKUP_STANDBY,
$oBackupManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_BACKUP_STANDBY));
$self->boolSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_CHECKSUM_PAGE,
$oBackupManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_CHECKSUM_PAGE));
$self->boolSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_COMPRESS,
$oBackupManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS));
$self->numericSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_FORMAT,
$oBackupManifest->numericGet(INI_SECTION_BACKREST, INI_KEY_FORMAT));
$self->boolSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_HARDLINK,
$oBackupManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_HARDLINK));
$self->boolSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_ONLINE,
$oBackupManifest->boolGet(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_ONLINE));
$self->numericSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_TIMESTAMP_START,
$oBackupManifest->numericGet(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TIMESTAMP_START));
$self->numericSet(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_TIMESTAMP_STOP,
$oBackupManifest->numericGet(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TIMESTAMP_STOP));
$self->set(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_TYPE,
$oBackupManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TYPE));
$self->set(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_VERSION,
$oBackupManifest->get(INI_SECTION_BACKREST, INI_KEY_VERSION));
if ($bRequired)
{
$self->set(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_HISTORY_ID,
$self->get(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_HISTORY_ID));
}
# If we are reconstructing, then the history id must be taken from the manifest
else
{
$self->set(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_HISTORY_ID,
$oBackupManifest->get(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_ID));
}
if (!$oBackupManifest->test(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TYPE, undef, CFGOPTVAL_BACKUP_TYPE_FULL))
{
my @stryReference = sort(keys(%$oReferenceHash));
$self->set(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_PRIOR,
$oBackupManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_PRIOR));
$self->set(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_REFERENCE,
\@stryReference);
}
if ($bSave)
{
$self->save();
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# current
#
# Test if a backup is current.
####################################################################################################################################
sub current
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strBackup
) =
logDebugParam
(
__PACKAGE__ . '->current', \@_,
{name => 'strBackup'}
);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'bTest', value => $self->test(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackup)}
);
}
####################################################################################################################################
# list
#
# Get backup keys.
####################################################################################################################################
sub list
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strFilter,
$strOrder
) =
logDebugParam
(
__PACKAGE__ . '->list', \@_,
{name => 'strFilter', required => false},
{name => 'strOrder', default => 'forward'}
);
# List of backups
my @stryBackup;
# Iterate through the backups and filter
for my $strBackup ($self->keys(INFO_BACKUP_SECTION_BACKUP_CURRENT))
{
if (!defined($strFilter) || $strBackup =~ $strFilter)
{
if ($strOrder eq 'reverse')
{
unshift(@stryBackup, $strBackup)
}
else
{
push(@stryBackup, $strBackup)
}
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'stryBackup', value => \@stryBackup}
);
}
####################################################################################################################################
# backupArchiveDbHistoryId
#
# Gets the backup.info db-id for the archiveId passed.
####################################################################################################################################
sub backupArchiveDbHistoryId
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strArchiveId,
$strPathBackupArchive,
) =
logDebugParam
(
__PACKAGE__ . '->backupArchiveDbHistoryId', \@_,
{name => 'strArchiveId'},
{name => 'strPathBackupArchive'},
);
# List of backups associated with the db-id provided
my @stryArchiveBackup;
# Build the db list from the history in the backup info and archive info file
my $oArchiveInfo = new pgBackRest::Archive::Info($strPathBackupArchive, true);
my $hDbListArchive = $oArchiveInfo->dbHistoryList();
my $hDbListBackup = $self->dbHistoryList();
my $iDbHistoryId = undef;
# Get the db-version and db-id (history id) from the archiveId
my ($strDbVersionArchive, $iDbIdArchive) = split("-", $strArchiveId);
# Get the DB system ID to map back to the backup info if it exists in the archive info file
if (exists($hDbListArchive->{$iDbIdArchive}))
{
my $ullDbSysIdArchive = $$hDbListArchive{$iDbIdArchive}{&INFO_SYSTEM_ID};
# Get the db-id from backup info history that corresponds to the archive db-version and db-system-id
# Sort from newest (highest db-id) to oldest
foreach my $iDbIdBackup (sort {$b <=> $a} keys %{$hDbListBackup})
{
if ($$hDbListBackup{$iDbIdBackup}{&INFO_SYSTEM_ID} == $ullDbSysIdArchive &&
$$hDbListBackup{$iDbIdBackup}{&INFO_DB_VERSION} eq $strDbVersionArchive)
{
$iDbHistoryId = $iDbIdBackup;
last;
}
}
}
# If the database is not found in the backup.info history list
if (!defined($iDbHistoryId))
{
# Check to see that the current DB sections match for the archive and backup info files
if (!($oArchiveInfo->test(INFO_ARCHIVE_SECTION_DB, INFO_ARCHIVE_KEY_DB_VERSION, undef,
($self->get(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_DB_VERSION)))) ||
!($oArchiveInfo->test(INFO_ARCHIVE_SECTION_DB, INFO_ARCHIVE_KEY_DB_SYSTEM_ID, undef,
($self->get(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_SYSTEM_ID)))))
{
# This should never happen unless the backup.info file is corrupt
confess &log(ASSERT, "the archive and backup database sections do not match", ERROR_FILE_INVALID);
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'iDbHistoryId', value => $iDbHistoryId}
);
}
####################################################################################################################################
# listByArchiveId
#
# Filters a list of backups by the archiveId passed.
####################################################################################################################################
sub listByArchiveId
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strArchiveId,
$strPathBackupArchive,
$stryBackup,
$strOrder,
) =
logDebugParam
(
__PACKAGE__ . '->listByArchiveId', \@_,
{name => 'strArchiveId'},
{name => 'strPathBackupArchive'},
{name => 'stryBackup'},
{name => 'strOrder', default => 'forward'}
);
# List of backups associated with the db-id provided
my @stryArchiveBackup;
my $iDbHistoryId = $self->backupArchiveDbHistoryId($strArchiveId, $strPathBackupArchive);
# If history found, then build list of backups associated with the archive id passed, else return empty array
if (defined($iDbHistoryId))
{
# Iterate through the backups and filter
foreach my $strBackup (@$stryBackup)
{
# From the backup.info current backup section, get the db-id for the backup and if it is the same, add to the list
if ($self->test(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackup, INFO_BACKUP_KEY_HISTORY_ID, $iDbHistoryId))
{
if ($strOrder eq 'reverse')
{
unshift(@stryArchiveBackup, $strBackup)
}
else
{
push(@stryArchiveBackup, $strBackup)
}
}
}
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'stryArchiveBackup', value => \@stryArchiveBackup}
);
}
####################################################################################################################################
# last
#
# Find the last backup depending on the type.
####################################################################################################################################
sub last
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strType
) =
logDebugParam
(
__PACKAGE__ . '->last', \@_,
{name => 'strType'}
);
my $strFilter = backupRegExpGet(true, $strType ne CFGOPTVAL_BACKUP_TYPE_FULL, $strType eq CFGOPTVAL_BACKUP_TYPE_INCR);
my $strBackup = ($self->list($strFilter, 'reverse'))[0];
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'strBackup', value => $strBackup}
);
}
####################################################################################################################################
# delete
#
# Delete a backup from the info file.
####################################################################################################################################
sub delete
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strBackupLabel
) =
logDebugParam
(
__PACKAGE__ . '->delete', \@_,
{name => 'strBackupLabel'}
);
$self->remove(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel);
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# create
#
# Create the info file. WARNING - this file should only be called from stanza-create or test modules.
####################################################################################################################################
sub create
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strDbVersion,
$ullDbSysId,
$iControlVersion,
$iCatalogVersion,
$bSave,
) =
logDebugParam
(
__PACKAGE__ . '->create', \@_,
{name => 'strDbVersion'},
{name => 'ullDbSysId'},
{name => 'iControlVersion'},
{name => 'iCatalogVersion'},
{name => 'bSave', default => true},
);
# Fill db section and db history section
$self->dbSectionSet($strDbVersion, $iControlVersion, $iCatalogVersion, $ullDbSysId, $self->dbHistoryIdGet(false));
if ($bSave)
{
$self->save();
}
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# dbHistoryIdGet
#
# Get the db history ID
####################################################################################################################################
sub dbHistoryIdGet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$bFileRequired,
) = logDebugParam
(
__PACKAGE__ . '->dbHistoryIdGet', \@_,
{name => 'bFileRequired', default => true},
);
# Confirm the info file exists if it is required
if ($bFileRequired)
{
$self->confirmExists();
}
# If the DB section does not exist, initialize the history to one, else return the latest ID
my $iDbHistoryId = (!$self->test(INFO_BACKUP_SECTION_DB))
? 1 : $self->numericGet(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_HISTORY_ID);
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'iDbHistoryId', value => $iDbHistoryId}
);
}
####################################################################################################################################
# dbHistoryList
#
# Get the data from the db history section.
####################################################################################################################################
sub dbHistoryList
{
my $self = shift;
my
(
$strOperation,
) = logDebugParam
(
__PACKAGE__ . '->dbHistoryList',
);
my %hDbHash;
foreach my $iHistoryId ($self->keys(INFO_BACKUP_SECTION_DB_HISTORY))
{
$hDbHash{$iHistoryId}{&INFO_DB_VERSION} =
$self->get(INFO_BACKUP_SECTION_DB_HISTORY, $iHistoryId, INFO_BACKUP_KEY_DB_VERSION);
$hDbHash{$iHistoryId}{&INFO_SYSTEM_ID} =
$self->get(INFO_BACKUP_SECTION_DB_HISTORY, $iHistoryId, INFO_BACKUP_KEY_SYSTEM_ID);
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'hDbHash', value => \%hDbHash}
);
}
####################################################################################################################################
# dbSectionSet
#
# Set the db and db:history sections.
####################################################################################################################################
sub dbSectionSet
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strDbVersion,
$iControlVersion,
$iCatalogVersion,
$ullDbSysId,
$iDbHistoryId,
) =
logDebugParam
(
__PACKAGE__ . '->dbSectionSet', \@_,
{name => 'strDbVersion', trace => true},
{name => 'iControlVersion', trace => true},
{name => 'iCatalogVersion', trace => true},
{name => 'ullDbSysId', trace => true},
{name => 'iDbHistoryId', trace => true},
);
# Fill db section
$self->numericSet(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_CATALOG, undef, $iCatalogVersion);
$self->numericSet(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_CONTROL, undef, $iControlVersion);
$self->numericSet(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_SYSTEM_ID, undef, $ullDbSysId);
$self->set(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_DB_VERSION, undef, $strDbVersion);
$self->numericSet(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_HISTORY_ID, undef, $iDbHistoryId);
# Fill db history
$self->numericSet(INFO_BACKUP_SECTION_DB_HISTORY, $iDbHistoryId, INFO_BACKUP_KEY_CATALOG, $iCatalogVersion);
$self->numericSet(INFO_BACKUP_SECTION_DB_HISTORY, $iDbHistoryId, INFO_BACKUP_KEY_CONTROL, $iControlVersion);
$self->numericSet(INFO_BACKUP_SECTION_DB_HISTORY, $iDbHistoryId, INFO_BACKUP_KEY_SYSTEM_ID, $ullDbSysId);
$self->set(INFO_BACKUP_SECTION_DB_HISTORY, $iDbHistoryId, INFO_BACKUP_KEY_DB_VERSION, $strDbVersion);
# Return from function and log return values if any
return logDebugReturn($strOperation);
}
####################################################################################################################################
# confirmDb
#
# Ensure that the backup is associated with the database passed.
# NOTE: The backup must exist in the backup:current section.
####################################################################################################################################
sub confirmDb
{
my $self = shift;
# Assign function parameters, defaults, and log debug info
my
(
$strOperation,
$strBackup,
$strDbVersion,
$ullDbSysId,
) =
logDebugParam
(
__PACKAGE__ . '->confirmDb', \@_,
{name => 'strBackup', trace => true},
{name => 'strDbVersion', trace => true},
{name => 'ullDbSysId', trace => true},
);
my $bConfirmDb = undef;
# Get the db-id associated with the backup
my $iDbHistoryId = $self->get(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackup, INFO_BACKUP_KEY_HISTORY_ID);
# Get the version and system-id for all known databases
my $hDbList = $self->dbHistoryList();
# If the db-id for the backup exists in the list
if (exists $hDbList->{$iDbHistoryId})
{
# If the version and system-id match then datbase is confirmed for the backup
if (($hDbList->{$iDbHistoryId}{&INFO_DB_VERSION} eq $strDbVersion) &&
($hDbList->{$iDbHistoryId}{&INFO_SYSTEM_ID} eq $ullDbSysId))
{
$bConfirmDb = true;
}
else
{
$bConfirmDb = false;
}
}
# If not, the backup.info file must be corrupt
else
{
confess &log(ERROR, "backup info file is missing database history information for an existing backup", ERROR_FILE_INVALID);
}
# Return from function and log return values if any
return logDebugReturn
(
$strOperation,
{name => 'bConfirmDb', value => $bConfirmDb}
);
}
####################################################################################################################################
# confirmExists
#
# Ensure that the backup.info file and the db section exist.
####################################################################################################################################
sub confirmExists
{
my $self = shift;
# Confirm the file exists and the DB section is filled out
if (!$self->test(INFO_BACKUP_SECTION_DB) || !$self->{bExists})
{
confess &log(ERROR, $self->{strBackupClusterPath} . "/" . $strBackupInfoMissingMsg, ERROR_FILE_MISSING);
}
}
1;