1
0
mirror of https://github.com/pgbackrest/pgbackrest.git synced 2024-12-14 10:13:05 +02:00
pgbackrest/test/lib/pgBackRestTest/Env/Host/HostDbTest.pm
David Steele d038b9a029 Support configurable WAL segment size.
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.
2018-09-25 10:24:42 +01:00

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;