1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-14 10:13:05 +02:00
pgbackrest/lib/BackRest/BackupInfo.pm
David Steele 148836fe44 Implemented issue #26: Info command.
* Includes updating the manifest to format 4.  It turns out the manifest and .info files were not very good for providing information.  A format update was required anyway so worked through the backlog of changes that would require a format change.

* Multiple database versions are now supported in the archive.  Does't actually work yet but the structure should be good.

* Tests use more constants now that test logs can catch name regressions.
2015-06-13 18:25:49 -04:00

346 lines
18 KiB
Perl

####################################################################################################################################
# BACKUP INFO MODULE
####################################################################################################################################
package BackRest::BackupInfo;
use parent 'BackRest::Ini';
use strict;
use warnings FATAL => qw(all);
use Carp qw(confess);
use Exporter qw(import);
use File::Basename qw(dirname basename);
use File::stat;
use lib dirname($0);
use BackRest::Config;
use BackRest::Exception;
use BackRest::File;
use BackRest::Ini;
use BackRest::Manifest;
use BackRest::Utility;
####################################################################################################################################
# Operation constants
####################################################################################################################################
use constant OP_BACKUP_INFO => 'BackupInfo';
use constant OP_INFO_BACKUP_NEW => OP_BACKUP_INFO . "->new";
use constant OP_INFO_BACKUP_BACKUP_ADD => OP_BACKUP_INFO . "->backupAdd";
use constant OP_INFO_BACKUP_BACKUP_REMOVE => OP_BACKUP_INFO . "->backupRemove";
####################################################################################################################################
# File/path constants
####################################################################################################################################
use constant FILE_BACKUP_INFO => 'backup.info'; our @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_SECTION_BACKUP_HISTORY => INFO_BACKUP_SECTION_BACKUP . ':history';
push @EXPORT, qw(INFO_BACKUP_SECTION_BACKUP_HISTORY);
use constant INFO_BACKUP_SECTION_DB => 'db';
push @EXPORT, qw(INFO_BACKUP_SECTION_DB);
use constant INFO_BACKUP_SECTION_DB_HISTORY => INFO_BACKUP_SECTION_DB . ':history';
push @EXPORT, qw(INFO_BACKUP_SECTION_DB_HISTORY);
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_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 => INI_KEY_CHECKSUM;
push @EXPORT, qw(INFO_BACKUP_KEY_CHECKSUM);
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 => '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_START_STOP => MANIFEST_KEY_START_STOP;
push @EXPORT, qw(INFO_BACKUP_KEY_START_STOP);
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);
####################################################################################################################################
# CONSTRUCTOR
####################################################################################################################################
sub new
{
my $class = shift; # Class name
my $strBackupClusterPath = shift; # Backup cluster path
&log(DEBUG, OP_INFO_BACKUP_NEW . ": backupClusterPath = ${strBackupClusterPath}");
# Build the backup info path/file name
my $strBackupInfoFile = "${strBackupClusterPath}/" . FILE_BACKUP_INFO;
my $bExists = -e $strBackupInfoFile ? true : false;
# Init object and store variables
my $self = $class->SUPER::new($strBackupInfoFile, $bExists);
$self->set(INI_SECTION_BACKREST, INI_KEY_VERSION, undef, version_get());
$self->{bExists} = $bExists;
$self->{strBackupClusterPath} = $strBackupClusterPath;
# Validate the backup info
$self->validate();
return $self;
}
####################################################################################################################################
# validate
#
# Compare the backup info against the actual backups on disk.
####################################################################################################################################
sub validate
{
my $self = shift;
# Remove backups that no longer exist on disk
foreach my $strBackup ($self->keys(INFO_BACKUP_SECTION_BACKUP_CURRENT))
{
if (!-e "$self->{strBackupClusterPath}/${strBackup}")
{
&log(WARN, "backup ${strBackup} is missing from the repository - removed from " . FILE_BACKUP_INFO);
$self->remove(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackup);
}
}
}
####################################################################################################################################
# check
#
# Check db info and make it is compatible.
####################################################################################################################################
sub check
{
my $self = shift;
my $oBackupManifest = shift;
my $iCatalogVersion = $oBackupManifest->getNumeric(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_CATALOG);
my $iControlVersion = $oBackupManifest->getNumeric(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_CONTROL);
my $ullDbSysId = $oBackupManifest->getNumeric(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_SYSTEM_ID);
my $strDbVersion = $oBackupManifest->get(MANIFEST_SECTION_BACKUP_DB, MANIFEST_KEY_DB_VERSION);
if (!$self->test(INFO_BACKUP_SECTION_DB))
{
my $iHistoryId = 1;
# Fill db section
$self->setNumeric(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_CATALOG, undef, $iCatalogVersion);
$self->setNumeric(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_CONTROL, undef, $iControlVersion);
$self->setNumeric(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->setNumeric(INFO_BACKUP_SECTION_DB, INFO_BACKUP_KEY_HISTORY_ID, undef, $iHistoryId);
# Fill db history
$self->setNumeric(INFO_BACKUP_SECTION_DB_HISTORY, $iHistoryId, INFO_BACKUP_KEY_CATALOG, $iCatalogVersion);
$self->setNumeric(INFO_BACKUP_SECTION_DB_HISTORY, $iHistoryId, INFO_BACKUP_KEY_CONTROL, $iControlVersion);
$self->setNumeric(INFO_BACKUP_SECTION_DB_HISTORY, $iHistoryId, INFO_BACKUP_KEY_SYSTEM_ID, $ullDbSysId);
$self->set(INFO_BACKUP_SECTION_DB_HISTORY, $iHistoryId, INFO_BACKUP_KEY_DB_VERSION, $strDbVersion);
}
else
{
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) .
"\nHINT: are you backing up to 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) .
"\nHINT: this may be a symptom of database or repository corruption!", ERROR_BACKUP_MISMATCH);
}
}
}
####################################################################################################################################
# backupAdd
#
# Add a backup to the info file.
####################################################################################################################################
sub backupAdd
{
my $self = shift;
my $oFile = shift;
my $oBackupManifest = shift;
my $strBackupLabel = $oBackupManifest->get(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_LABEL);
&log(DEBUG, OP_INFO_BACKUP_BACKUP_ADD . ": backupLabel = ${strBackupLabel}");
# Calculate backup sizes and references
my $lBackupSize = 0;
my $lBackupSizeDelta = 0;
my $lBackupRepoSize = 0;
my $lBackupRepoSizeDelta = 0;
my $oReferenceHash = undef;
my $bCompress = $oBackupManifest->get(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS);
foreach my $strPathKey ($oBackupManifest->keys(MANIFEST_SECTION_BACKUP_PATH))
{
my $strFileSection = "${strPathKey}:" . MANIFEST_FILE;
my $strPath = $strPathKey;
$strPath =~ s/\:/\//g;
foreach my $strFileKey ($oBackupManifest->keys($strFileSection))
{
my $lFileSize = $oBackupManifest->get($strFileSection, $strFileKey, MANIFEST_SUBKEY_SIZE);
my $strFileReference = $oBackupManifest->get($strFileSection, $strFileKey, MANIFEST_SUBKEY_REFERENCE, false);
my $strFile = $oFile->path_get(PATH_BACKUP_CLUSTER,
(defined($strFileReference) ? "${strFileReference}" : $strBackupLabel) .
"/${strPath}/${strFileKey}" .
($bCompress ? '.' . $oFile->{strCompressExtension} : ''));
my $oStat = lstat($strFile);
# Check for errors in stat
defined($oStat)
or confess &log(ERROR, "unable to lstat ${strFile}");
$lBackupSize += $lFileSize;
$lBackupRepoSize += $oStat->size;
if (defined($strFileReference))
{
$$oReferenceHash{$strFileReference} = true;
}
else
{
$lBackupSizeDelta += $lFileSize;
$lBackupRepoSizeDelta += $oStat->size;
}
}
}
# Add the manifest.backup size
my $strManifestFile = $oFile->path_get(PATH_BACKUP_CLUSTER, "/${strBackupLabel}/" . FILE_MANIFEST);
my $oStat = lstat($strManifestFile);
# Check for errors in stat
defined($oStat)
or confess &log(ERROR, "unable to lstat ${strManifestFile}");
$lBackupRepoSize += $oStat->size;
$lBackupRepoSizeDelta += $oStat->size;
# Set backup size info
$self->setNumeric(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_BACKUP_SIZE, $lBackupSize);
$self->setNumeric(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_BACKUP_SIZE_DELTA, $lBackupSizeDelta);
$self->setNumeric(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_BACKUP_REPO_SIZE, $lBackupRepoSize);
$self->setNumeric(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_BACKUP_REPO_SIZE_DELTA,
$lBackupRepoSizeDelta);
# Store information about the backup into the backup section
$self->setBool(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_ARCHIVE_CHECK,
$oBackupManifest->getBool(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_ARCHIVE_CHECK));
$self->setBool(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_ARCHIVE_COPY,
$oBackupManifest->getBool(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->setBool(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_COMPRESS,
$oBackupManifest->getBool(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_COMPRESS));
$self->setNumeric(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_FORMAT,
$oBackupManifest->getNumeric(INI_SECTION_BACKREST, INI_KEY_FORMAT));
$self->setBool(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_HARDLINK,
$oBackupManifest->getBool(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_HARDLINK));
$self->setBool(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_START_STOP,
$oBackupManifest->getBool(MANIFEST_SECTION_BACKUP_OPTION, MANIFEST_KEY_START_STOP));
$self->setNumeric(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_TIMESTAMP_START,
$oBackupManifest->getNumeric(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TIMESTAMP_START));
$self->setNumeric(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel, INFO_BACKUP_KEY_TIMESTAMP_STOP,
$oBackupManifest->getNumeric(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));
$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 (!$oBackupManifest->test(MANIFEST_SECTION_BACKUP, MANIFEST_KEY_TYPE, undef, 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);
}
# Copy backup info to the history
$self->set(INFO_BACKUP_SECTION_BACKUP_HISTORY, $strBackupLabel, undef,
$self->get(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel));
$self->save();
}
####################################################################################################################################
# backupRemove
#
# Remove a backup from the info file.
####################################################################################################################################
sub backupRemove
{
my $self = shift;
my $strBackupLabel = shift;
&log(DEBUG, OP_INFO_BACKUP_BACKUP_REMOVE . ": backupLabel = ${strBackupLabel}");
$self->remove(INFO_BACKUP_SECTION_BACKUP_CURRENT, $strBackupLabel);
$self->save();
}
1;