mirror of
https://github.com/pgbackrest/pgbackrest.git
synced 2024-12-14 10:13:05 +02:00
d038b9a029
PostgreSQL 11 introduces configurable WAL segment sizes, from 1MB to 1GB. There are two areas that needed to be updated to support this: building the archive-get queue and checking that WAL has been archived after a backup. Both operations require the WAL segment size to properly build a list. Checking the archive after a backup is still implemented in Perl and has an active database connection, so just get the WAL segment size from the database. The archive-get command does not have a connection to the database, so get the WAL segment size from pg_control instead. This requires a deeper inspection of pg_control than has been done in the past, so it seemed best to copy the relevant data structures from each version of PostgreSQL and build a generic interface layer to address them. While this approach is a bit verbose, it has the advantage of being relatively simple, and can easily be updated for new versions of PostgreSQL. Since the integration tests generate pg_control files for testing, teach Perl how to generate files with the correct offsets for both 32-bit and 64-bit architectures.
513 lines
18 KiB
Perl
513 lines
18 KiB
Perl
####################################################################################################################################
|
|
# HostDbTest.pm - Database host
|
|
####################################################################################################################################
|
|
package pgBackRestTest::Env::Host::HostDbTest;
|
|
use parent 'pgBackRestTest::Env::Host::HostDbCommonTest';
|
|
|
|
####################################################################################################################################
|
|
# Perl includes
|
|
####################################################################################################################################
|
|
use strict;
|
|
use warnings FATAL => qw(all);
|
|
use Carp qw(confess);
|
|
|
|
use DBI;
|
|
use Exporter qw(import);
|
|
our @EXPORT = qw();
|
|
use File::Basename qw(basename);
|
|
|
|
use pgBackRest::Common::Exception;
|
|
use pgBackRest::Common::Log;
|
|
use pgBackRest::Common::String;
|
|
use pgBackRest::Common::Wait;
|
|
use pgBackRest::DbVersion;
|
|
use pgBackRest::Manifest;
|
|
use pgBackRest::Protocol::Storage::Helper;
|
|
use pgBackRest::Version;
|
|
|
|
use pgBackRestTest::Env::Host::HostBackupTest;
|
|
use pgBackRestTest::Env::Host::HostBaseTest;
|
|
use pgBackRestTest::Env::Host::HostDbCommonTest;
|
|
use pgBackRestTest::Common::ContainerTest;
|
|
use pgBackRestTest::Common::RunTest;
|
|
|
|
####################################################################################################################################
|
|
# Db defaults
|
|
####################################################################################################################################
|
|
use constant HOST_DB_DEFAULT => 'postgres';
|
|
use constant HOST_DB_TIMEOUT => 30;
|
|
|
|
####################################################################################################################################
|
|
# new
|
|
####################################################################################################################################
|
|
sub new
|
|
{
|
|
my $class = shift; # Class name
|
|
|
|
# Assign function parameters, defaults, and log debug info
|
|
my
|
|
(
|
|
$strOperation,
|
|
$oParam,
|
|
) =
|
|
logDebugParam
|
|
(
|
|
__PACKAGE__ . '->new', \@_,
|
|
{name => 'oParam', required => false, trace => true},
|
|
);
|
|
|
|
# Get db version
|
|
my $strDbVersion = testRunGet()->pgVersion();
|
|
|
|
my $self = $class->SUPER::new(
|
|
{
|
|
strImage => containerRepo() . ':' . testRunGet()->vm() . "-test",
|
|
strBackupDestination => $$oParam{strBackupDestination},
|
|
oLogTest => $$oParam{oLogTest},
|
|
bStandby => $$oParam{bStandby},
|
|
bRepoLocal => $oParam->{bRepoLocal},
|
|
bRepoEncrypt => $oParam->{bRepoEncrypt},
|
|
});
|
|
bless $self, $class;
|
|
|
|
# Set parameters
|
|
$self->{strPgSocketPath} = $self->dbPath();
|
|
$self->{iPgPort} = defined($$oParam{bStandby}) && $$oParam{bStandby} ? 6544 : 6543;
|
|
|
|
$self->{strPgLogPath} = $self->testPath();
|
|
$self->{strPgLogFile} = $self->pgLogPath() . '/postgresql.log';
|
|
|
|
# Get Db version
|
|
if (defined($strDbVersion))
|
|
{
|
|
my $strOutLog = $self->executeSimple($self->pgBinPath() . '/postgres --version');
|
|
|
|
my @stryVersionToken = split(/ /, $strOutLog);
|
|
@stryVersionToken = split(/\./, $stryVersionToken[2]);
|
|
my $strDbVersionActual =
|
|
trim($stryVersionToken[0]) .
|
|
(defined($stryVersionToken[1]) && trim($stryVersionToken[0]) < 10 ? '.' . trim($stryVersionToken[1]) : '');
|
|
|
|
# Warn if this is a devel/alpha/beta version
|
|
my $strVersionRegExp = '(devel|((alpha|beta|rc)[0-9]+))$';
|
|
|
|
if ($strDbVersionActual =~ /$strVersionRegExp/)
|
|
{
|
|
my $strDevVersion = $strDbVersionActual;
|
|
$strDbVersionActual =~ s/$strVersionRegExp//;
|
|
$strDevVersion = substr($strDevVersion, length($strDbVersionActual));
|
|
|
|
if (!defined($$oParam{bStandby}) || !$$oParam{bStandby})
|
|
{
|
|
&log(WARN, "Testing against ${strDbVersionActual} ${strDevVersion} version");
|
|
}
|
|
}
|
|
|
|
# Don't run unit tests for unsupported versions
|
|
my @stryVersionSupport = versionSupport();
|
|
|
|
if ($strDbVersionActual < $stryVersionSupport[0])
|
|
{
|
|
confess &log(ERROR, "only PostgreSQL version $stryVersionSupport[0] and up are supported");
|
|
}
|
|
|
|
if ($strDbVersion ne $strDbVersionActual)
|
|
{
|
|
confess &log(ERROR, "actual database version ${strDbVersionActual} does not match expected version ${strDbVersion}");
|
|
}
|
|
}
|
|
|
|
# Create wal directory
|
|
storageTest()->pathCreate($self->dbPath() . '/pg_' . $self->walId(), {strMode => '0700'});
|
|
|
|
# Return from function and log return values if any
|
|
return logDebugReturn
|
|
(
|
|
$strOperation,
|
|
{name => 'self', value => $self, trace => true}
|
|
);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# sqlConnect
|
|
####################################################################################################################################
|
|
sub sqlConnect
|
|
{
|
|
my $self = shift;
|
|
my $hParam = shift;
|
|
|
|
# Set defaults
|
|
my $iTimeout = defined($$hParam{iTimeout}) ? $$hParam{iTimeout} : HOST_DB_TIMEOUT;
|
|
my $strDb = defined($$hParam{strDb}) ? $$hParam{strDb} : HOST_DB_DEFAULT;
|
|
|
|
# If not connected
|
|
if (!defined($self->{db}{$strDb}{hDb}))
|
|
{
|
|
# Retry until connection is successful
|
|
my $oWait = waitInit($iTimeout);
|
|
|
|
do
|
|
{
|
|
# Connect to the db (whether it is local or remote)
|
|
$self->{db}{$strDb}{hDb} =
|
|
DBI->connect(
|
|
"dbi:Pg:dbname=${strDb};port=" . $self->pgPort() . ';host=' . $self->pgSocketPath(),
|
|
$self->userGet(), undef,
|
|
{AutoCommit => 0, RaiseError => 0, PrintError => 0});
|
|
|
|
return $self->{db}{$strDb}{hDb} if $self->{db}{$strDb}{hDb};
|
|
}
|
|
while (!defined($self->{db}{$strDb}{hDb}) && waitMore($oWait));
|
|
|
|
# Error if unable to connect
|
|
if (!defined($self->{db}{$strDb}{hDb}))
|
|
{
|
|
confess &log(ERROR, "unable to connect to PostgreSQL after ${iTimeout} second(s):\n" . $DBI::errstr, ERROR_DB_CONNECT);
|
|
}
|
|
}
|
|
|
|
return $self->{db}{$strDb}{hDb};
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# sqlDisconnect
|
|
####################################################################################################################################
|
|
sub sqlDisconnect
|
|
{
|
|
my $self = shift;
|
|
my $hParam = shift;
|
|
|
|
foreach my $strDb (keys(%{$self->{db}}))
|
|
{
|
|
if (defined($$hParam{$strDb}) && $$hParam{$strDb} ne $strDb)
|
|
{
|
|
next;
|
|
}
|
|
|
|
if (defined($self->{db}{$strDb}{hDb}))
|
|
{
|
|
$self->{db}{$strDb}{hDb}->disconnect();
|
|
undef($self->{db}{$strDb}{hDb});
|
|
}
|
|
}
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# sqlExecute
|
|
####################################################################################################################################
|
|
sub sqlExecute
|
|
{
|
|
my $self = shift;
|
|
my $strSql = shift;
|
|
my $hParam = shift;
|
|
|
|
# Set defaults
|
|
my $bCheckPoint = defined($$hParam{bCheckPoint}) ? $$hParam{bCheckPoint} : false;
|
|
my $bCommit = defined($$hParam{bCommit}) ? $$hParam{bCommit} : true;
|
|
|
|
# Get the db handle
|
|
my $hDb = $self->sqlConnect({strDb => $$hParam{strDb}});
|
|
|
|
# Set autocommit on/off
|
|
$hDb->{AutoCommit} = defined($$hParam{bAutoCommit}) ? ($$hParam{bAutoCommit} ? true : false) : false;
|
|
|
|
# Log and execute the statement
|
|
&log(DETAIL, "SQL: ${strSql}");
|
|
|
|
my $hStatement = $hDb->prepare($strSql);
|
|
|
|
$hStatement->execute() or
|
|
confess &log(ERROR, "Unable to execute: ${strSql}\n" . $DBI::errstr);
|
|
$hStatement->finish();
|
|
|
|
if ($bCommit && !$hDb->{AutoCommit})
|
|
{
|
|
$self->sqlCommit();
|
|
}
|
|
|
|
# Perform a checkpoint if requested
|
|
if ($bCheckPoint)
|
|
{
|
|
$self->sqlExecute('checkpoint', {bCommit => false, bCheckPoint => false});
|
|
}
|
|
|
|
# Set autocommit off
|
|
$hDb->{AutoCommit} = 0;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# sqlSelect
|
|
####################################################################################################################################
|
|
sub sqlSelect
|
|
{
|
|
my $self = shift;
|
|
my $strSql = shift;
|
|
my $hParam = shift;
|
|
|
|
# Get the db handle
|
|
my $hDb = $self->sqlConnect({strDb => $$hParam{strDb}});
|
|
|
|
# Log and execute the statement
|
|
&log(DEBUG, (defined($$hParam{strDb}) ? "DB: $$hParam{strDb}, " : "") . "SQL: ${strSql}");
|
|
my $hStatement = $hDb->prepare($strSql);
|
|
|
|
$hStatement = $hDb->prepare($strSql);
|
|
|
|
$hStatement->execute() or
|
|
confess &log(ERROR, "Unable to execute: ${strSql}\n" . $DBI::errstr);
|
|
|
|
my @oyRow = $hStatement->fetchrow_array();
|
|
|
|
$hStatement->finish();
|
|
|
|
return @oyRow;
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# sqlSelectOne
|
|
####################################################################################################################################
|
|
sub sqlSelectOne
|
|
{
|
|
my $self = shift;
|
|
my $strSql = shift;
|
|
my $hParam = shift;
|
|
|
|
return ($self->sqlSelect($strSql, $hParam))[0];
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# sqlSelectOneTest
|
|
####################################################################################################################################
|
|
sub sqlSelectOneTest
|
|
{
|
|
my $self = shift;
|
|
my $strSql = shift;
|
|
my $strExpectedValue = shift;
|
|
my $hParam = shift;
|
|
|
|
# Set defaults
|
|
my $iTimeout = defined($$hParam{iTimeout}) ? $$hParam{iTimeout} : HOST_DB_TIMEOUT;
|
|
|
|
my $lStartTime = time();
|
|
my $strActualValue;
|
|
|
|
do
|
|
{
|
|
$self->sqlConnect($hParam);
|
|
$strActualValue = $self->sqlSelectOne($strSql, $hParam);
|
|
|
|
if (defined($strActualValue) && $strActualValue eq $strExpectedValue)
|
|
{
|
|
return;
|
|
}
|
|
|
|
$self->sqlDisconnect();
|
|
}
|
|
while (defined($iTimeout) && (time() - $lStartTime) <= $iTimeout);
|
|
|
|
confess &log(
|
|
ERROR, "expected value '${strExpectedValue}' from '${strSql}' but actual was '" .
|
|
(defined($strActualValue) ? $strActualValue : '[undef]') . "'");
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# sqlCommit
|
|
####################################################################################################################################
|
|
sub sqlCommit
|
|
{
|
|
my $self = shift;
|
|
my $hParam = shift;
|
|
|
|
my $bCheckPoint = defined($$hParam{bCheckPoint}) ? $$hParam{bCheckPoint} : false;
|
|
|
|
$self->sqlExecute('commit', {bCommit => false, bCheckPoint => $bCheckPoint});
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# sqlWalRotate
|
|
####################################################################################################################################
|
|
sub sqlWalRotate
|
|
{
|
|
my $self = shift;
|
|
|
|
$self->sqlExecute('select pg_switch_' . $self->walId() . '()', {bCommit => false, bCheckPoint => false});
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# clusterCreate
|
|
#
|
|
# Create the PostgreSQL cluster and start it.
|
|
####################################################################################################################################
|
|
sub clusterCreate
|
|
{
|
|
my $self = shift;
|
|
my $hParam = shift;
|
|
|
|
# Set defaults
|
|
my $strWalPath = defined($$hParam{strWalPath}) ? $$hParam{strWalPath} : $self->dbPath() . '/pg_' . $self->walId();
|
|
|
|
# Don't link WAL directory for versions < 9.2 because some recovery scenarios won't work
|
|
$self->executeSimple(
|
|
$self->pgBinPath() . '/initdb ' .
|
|
($self->pgVersion() >= PG_VERSION_93 ? ' -k' : '') .
|
|
($self->pgVersion() >= PG_VERSION_92 ? ' --' . $self->walId() . "dir=${strWalPath}" : '') .
|
|
($self->pgVersion() >= PG_VERSION_11 ? ' --wal-segsize=1' : '') .
|
|
' --pgdata=' . $self->dbBasePath() . ' --auth=trust');
|
|
|
|
if (!$self->standby() && $self->pgVersion() >= PG_VERSION_HOT_STANDBY)
|
|
{
|
|
$self->executeSimple(
|
|
"echo 'host replication replicator db-standby trust' >> " . $self->dbBasePath() . '/pg_hba.conf');
|
|
}
|
|
|
|
$self->clusterStart(
|
|
{bHotStandby => $$hParam{bHotStandby}, bArchive => $$hParam{bArchive}, bArchiveAlways => $$hParam{bArchiveAlways},
|
|
bArchiveInvalid => $$hParam{bArchiveInvalid}});
|
|
|
|
if (!$self->standby() && $self->pgVersion() >= PG_VERSION_HOT_STANDBY)
|
|
{
|
|
$self->sqlExecute("create user replicator replication", {bCommit =>true});
|
|
}
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# clusterStart
|
|
#
|
|
# Start the PostgreSQL cluster with various test options.
|
|
####################################################################################################################################
|
|
sub clusterStart
|
|
{
|
|
my $self = shift;
|
|
my $hParam = shift;
|
|
|
|
# Set defaults
|
|
my $bHotStandby = defined($$hParam{bHotStandby}) ? $$hParam{bHotStandby} : false;
|
|
my $bArchive = defined($$hParam{bArchive}) ? $$hParam{bArchive} : true;
|
|
my $bArchiveAlways = defined($$hParam{bArchiveAlways}) ? $$hParam{bArchiveAlways} : false;
|
|
my $bArchiveInvalid = defined($$hParam{bArchiveInvalid}) ? $$hParam{bArchiveInvalid} : false;
|
|
my $bArchiveEnabled = defined($$hParam{bArchiveEnabled}) ? $$hParam{bArchiveEnabled} : true;
|
|
|
|
# Make sure postgres is not running
|
|
if (-e $self->dbBasePath() . '/postmaster.pid')
|
|
{
|
|
confess 'postmaster.pid exists';
|
|
}
|
|
|
|
# Create the archive command
|
|
my $strArchive =
|
|
$self->backrestExe() . ' --stanza=' . ($bArchiveInvalid ? 'bogus' : $self->stanza()) .
|
|
' --config=' . $self->backrestConfig() . ' archive-push %p';
|
|
|
|
# Start the cluster
|
|
my $strCommand =
|
|
$self->pgBinPath() . '/pg_ctl start -o "-c port=' . $self->pgPort() .
|
|
($self->pgVersion() < PG_VERSION_95 ? ' -c checkpoint_segments=1' : '');
|
|
|
|
if ($bArchiveEnabled)
|
|
{
|
|
if ($self->pgVersion() >= PG_VERSION_95 && $bArchiveAlways)
|
|
{
|
|
$strCommand .= " -c archive_mode=always";
|
|
}
|
|
else
|
|
{
|
|
$strCommand .= " -c archive_mode=on";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$strCommand .= " -c archive_mode=off";
|
|
}
|
|
|
|
if ($bArchive)
|
|
{
|
|
$strCommand .= " -c archive_command='${strArchive}'";
|
|
}
|
|
else
|
|
{
|
|
$strCommand .= " -c archive_command=true";
|
|
}
|
|
|
|
if ($self->pgVersion() >= PG_VERSION_90)
|
|
{
|
|
$strCommand .= ' -c wal_level=hot_standby -c hot_standby=' . ($bHotStandby ? 'on' : 'off');
|
|
}
|
|
|
|
$strCommand .=
|
|
($self->pgVersion() >= PG_VERSION_HOT_STANDBY ? ' -c max_wal_senders=3' : '') .
|
|
' -c listen_addresses=\'*\'' .
|
|
' -c log_directory=\'' . $self->pgLogPath() . "'" .
|
|
' -c log_filename=\'' . basename($self->pgLogFile()) . "'" .
|
|
' -c log_rotation_age=0' .
|
|
' -c log_rotation_size=0' .
|
|
' -c log_error_verbosity=verbose' .
|
|
' -c unix_socket_director' . ($self->pgVersion() < PG_VERSION_93 ? 'y=\'' : 'ies=\'') . $self->dbPath() . '\'"' .
|
|
' -D ' . $self->dbBasePath() . ' -l ' . $self->pgLogFile() . ' -s';
|
|
|
|
$self->executeSimple($strCommand);
|
|
|
|
# Connect user session
|
|
$self->sqlConnect();
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# clusterStop
|
|
#
|
|
# Stop the PostgreSQL cluster and optionally check for errors in the server log.
|
|
####################################################################################################################################
|
|
sub clusterStop
|
|
{
|
|
my $self = shift;
|
|
my $hParam = shift;
|
|
|
|
# Set defaults
|
|
my $bIgnoreLogError = defined($$hParam{bIgnoreLogError}) ? $$hParam{bIgnoreLogError} : false;
|
|
|
|
# Disconnect user session
|
|
$self->sqlDisconnect();
|
|
|
|
# Grep for errors in postgresql.log - this is done first because we want to ignore any errors that happen during shutdown
|
|
if (!$bIgnoreLogError && storageTest()->exists($self->pgLogFile()))
|
|
{
|
|
$self->executeSimple(
|
|
'grep -v "FATAL\: 57P03\: the database system is starting up" ' . $self->pgLogFile() . ' | grep "ERROR\|FATAL"',
|
|
{iExpectedExitStatus => 1});
|
|
}
|
|
|
|
# If postmaster process is running then stop the cluster
|
|
if (-e $self->dbBasePath() . '/' . DB_FILE_POSTMASTERPID)
|
|
{
|
|
$self->executeSimple($self->pgBinPath() . '/pg_ctl stop -D ' . $self->dbBasePath() . ' -w -s -m fast');
|
|
}
|
|
|
|
# Remove the log file
|
|
storageTest()->remove($self->pgLogFile(), {bIgnoreMissing => true});
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# clusterRestart
|
|
#
|
|
# Restart the PostgreSQL cluster.
|
|
####################################################################################################################################
|
|
sub clusterRestart
|
|
{
|
|
my $self = shift;
|
|
my $hParam = shift;
|
|
|
|
$self->clusterStop($hParam);
|
|
$self->clusterStart($hParam);
|
|
}
|
|
|
|
####################################################################################################################################
|
|
# Getters
|
|
####################################################################################################################################
|
|
sub walId {return shift->pgVersion() >= PG_VERSION_10 ? 'wal' : 'xlog'}
|
|
sub pgBinPath {return testRunGet()->pgBinPath()}
|
|
sub pgLogFile {return shift->{strPgLogFile}}
|
|
sub pgLogPath {return shift->{strPgLogPath}}
|
|
sub pgPort {return shift->{iPgPort}}
|
|
sub pgSocketPath {return shift->{strPgSocketPath}}
|
|
sub pgVersion {return testRunGet()->pgVersion()}
|
|
|
|
1;
|