Back-patch src/test/recovery and PostgresNode from 9.6 to 9.5.
This omits 007_sync_rep.pl, which tests a feature new in 9.6. The only other change is to substitute "hot_standby" for "replica". A planned back-patch will use this suite to test its recovery behavior changes. Identified by Kyotaro Horiguchi, though I did not use his patch. Discussion: https://postgr.es/m/20200304.162919.898938381201316571.horikyota.ntt@gmail.com
This commit is contained in:
parent
c2c2e531e7
commit
12034da6cc
@ -371,12 +371,12 @@ ifeq ($(enable_tap_tests),yes)
|
|||||||
|
|
||||||
define prove_installcheck
|
define prove_installcheck
|
||||||
rm -rf $(CURDIR)/tmp_check/log
|
rm -rf $(CURDIR)/tmp_check/log
|
||||||
cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' top_builddir='$(CURDIR)/$(top_builddir)' PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) t/*.pl
|
cd $(srcdir) && TESTDIR='$(CURDIR)' PATH="$(bindir):$$PATH" PGPORT='6$(DEF_PGPORT)' top_builddir='$(CURDIR)/$(top_builddir)' PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' REGRESS_SHLIB='$(abs_top_builddir)/src/test/regress/regress$(DLSUFFIX)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) t/*.pl
|
||||||
endef
|
endef
|
||||||
|
|
||||||
define prove_check
|
define prove_check
|
||||||
rm -rf $(CURDIR)/tmp_check/log
|
rm -rf $(CURDIR)/tmp_check/log
|
||||||
cd $(srcdir) && TESTDIR='$(CURDIR)' $(with_temp_install) PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) t/*.pl
|
cd $(srcdir) && TESTDIR='$(CURDIR)' $(with_temp_install) PGPORT='6$(DEF_PGPORT)' PG_REGRESS='$(CURDIR)/$(top_builddir)/src/test/regress/pg_regress' REGRESS_SHLIB='$(abs_top_builddir)/src/test/regress/regress$(DLSUFFIX)' $(PROVE) $(PG_PROVE_FLAGS) $(PROVE_FLAGS) t/*.pl
|
||||||
endef
|
endef
|
||||||
|
|
||||||
else
|
else
|
||||||
|
@ -12,7 +12,7 @@ subdir = src/test
|
|||||||
top_builddir = ../..
|
top_builddir = ../..
|
||||||
include $(top_builddir)/src/Makefile.global
|
include $(top_builddir)/src/Makefile.global
|
||||||
|
|
||||||
SUBDIRS = perl regress isolation modules
|
SUBDIRS = perl regress isolation modules recovery
|
||||||
|
|
||||||
# We don't build or execute examples/, locale/, or thread/ by default,
|
# We don't build or execute examples/, locale/, or thread/ by default,
|
||||||
# but we do want "make clean" etc to recurse into them. Likewise for ssl/,
|
# but we do want "make clean" etc to recurse into them. Likewise for ssl/,
|
||||||
|
1481
src/test/perl/PostgresNode.pm
Normal file
1481
src/test/perl/PostgresNode.pm
Normal file
File diff suppressed because it is too large
Load Diff
154
src/test/perl/RecursiveCopy.pm
Normal file
154
src/test/perl/RecursiveCopy.pm
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=head1 NAME
|
||||||
|
|
||||||
|
RecursiveCopy - simple recursive copy implementation
|
||||||
|
|
||||||
|
=head1 SYNOPSIS
|
||||||
|
|
||||||
|
use RecursiveCopy;
|
||||||
|
|
||||||
|
RecursiveCopy::copypath($from, $to, filterfn => sub { return 1; });
|
||||||
|
RecursiveCopy::copypath($from, $to);
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
package RecursiveCopy;
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use File::Basename;
|
||||||
|
use File::Copy;
|
||||||
|
|
||||||
|
=pod
|
||||||
|
|
||||||
|
=head1 DESCRIPTION
|
||||||
|
|
||||||
|
=head2 copypath($from, $to, %params)
|
||||||
|
|
||||||
|
Recursively copy all files and directories from $from to $to.
|
||||||
|
Does not preserve file metadata (e.g., permissions).
|
||||||
|
|
||||||
|
Only regular files and subdirectories are copied. Trying to copy other types
|
||||||
|
of directory entries raises an exception.
|
||||||
|
|
||||||
|
Raises an exception if a file would be overwritten, the source directory can't
|
||||||
|
be read, or any I/O operation fails. However, we silently ignore ENOENT on
|
||||||
|
open, because when copying from a live database it's possible for a file/dir
|
||||||
|
to be deleted after we see its directory entry but before we can open it.
|
||||||
|
|
||||||
|
Always returns true.
|
||||||
|
|
||||||
|
If the B<filterfn> parameter is given, it must be a subroutine reference.
|
||||||
|
This subroutine will be called for each entry in the source directory with its
|
||||||
|
relative path as only parameter; if the subroutine returns true the entry is
|
||||||
|
copied, otherwise the file is skipped.
|
||||||
|
|
||||||
|
On failure the target directory may be in some incomplete state; no cleanup is
|
||||||
|
attempted.
|
||||||
|
|
||||||
|
=head1 EXAMPLES
|
||||||
|
|
||||||
|
RecursiveCopy::copypath('/some/path', '/empty/dir',
|
||||||
|
filterfn => sub {
|
||||||
|
# omit pg_log and contents
|
||||||
|
my $src = shift;
|
||||||
|
return $src ne 'pg_log';
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
=cut
|
||||||
|
|
||||||
|
sub copypath
|
||||||
|
{
|
||||||
|
my ($base_src_dir, $base_dest_dir, %params) = @_;
|
||||||
|
my $filterfn;
|
||||||
|
|
||||||
|
if (defined $params{filterfn})
|
||||||
|
{
|
||||||
|
die "if specified, filterfn must be a subroutine reference"
|
||||||
|
unless defined(ref $params{filterfn})
|
||||||
|
and (ref $params{filterfn} eq 'CODE');
|
||||||
|
|
||||||
|
$filterfn = $params{filterfn};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$filterfn = sub { return 1; };
|
||||||
|
}
|
||||||
|
|
||||||
|
# Complain if original path is bogus, because _copypath_recurse won't.
|
||||||
|
die "\"$base_src_dir\" does not exist" if !-e $base_src_dir;
|
||||||
|
|
||||||
|
# Start recursive copy from current directory
|
||||||
|
return _copypath_recurse($base_src_dir, $base_dest_dir, "", $filterfn);
|
||||||
|
}
|
||||||
|
|
||||||
|
# Recursive private guts of copypath
|
||||||
|
sub _copypath_recurse
|
||||||
|
{
|
||||||
|
my ($base_src_dir, $base_dest_dir, $curr_path, $filterfn) = @_;
|
||||||
|
my $srcpath = "$base_src_dir/$curr_path";
|
||||||
|
my $destpath = "$base_dest_dir/$curr_path";
|
||||||
|
|
||||||
|
# invoke the filter and skip all further operation if it returns false
|
||||||
|
return 1 unless &$filterfn($curr_path);
|
||||||
|
|
||||||
|
# Check for symlink -- needed only on source dir
|
||||||
|
# (note: this will fall through quietly if file is already gone)
|
||||||
|
die "Cannot operate on symlink \"$srcpath\"" if -l $srcpath;
|
||||||
|
|
||||||
|
# Abort if destination path already exists. Should we allow directories
|
||||||
|
# to exist already?
|
||||||
|
die "Destination path \"$destpath\" already exists" if -e $destpath;
|
||||||
|
|
||||||
|
# If this source path is a file, simply copy it to destination with the
|
||||||
|
# same name and we're done.
|
||||||
|
if (-f $srcpath)
|
||||||
|
{
|
||||||
|
my $fh;
|
||||||
|
unless (open($fh, '<', $srcpath))
|
||||||
|
{
|
||||||
|
return 1 if ($!{ENOENT});
|
||||||
|
die "open($srcpath) failed: $!";
|
||||||
|
}
|
||||||
|
copy($fh, $destpath)
|
||||||
|
or die "copy $srcpath -> $destpath failed: $!";
|
||||||
|
close $fh;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# If it's a directory, create it on dest and recurse into it.
|
||||||
|
if (-d $srcpath)
|
||||||
|
{
|
||||||
|
my $directory;
|
||||||
|
unless (opendir($directory, $srcpath))
|
||||||
|
{
|
||||||
|
return 1 if ($!{ENOENT});
|
||||||
|
die "opendir($srcpath) failed: $!";
|
||||||
|
}
|
||||||
|
|
||||||
|
mkdir($destpath) or die "mkdir($destpath) failed: $!";
|
||||||
|
|
||||||
|
while (my $entry = readdir($directory))
|
||||||
|
{
|
||||||
|
next if ($entry eq '.' or $entry eq '..');
|
||||||
|
_copypath_recurse($base_src_dir, $base_dest_dir,
|
||||||
|
$curr_path eq '' ? $entry : "$curr_path/$entry", $filterfn)
|
||||||
|
or die "copypath $srcpath/$entry -> $destpath/$entry failed";
|
||||||
|
}
|
||||||
|
|
||||||
|
closedir($directory);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# If it disappeared from sight, that's OK.
|
||||||
|
return 1 if !-e $srcpath;
|
||||||
|
|
||||||
|
# Else it's some weird file type; complain.
|
||||||
|
die "Source path \"$srcpath\" is not a regular file or directory";
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
@ -15,6 +15,7 @@ our @EXPORT = qw(
|
|||||||
psql
|
psql
|
||||||
slurp_dir
|
slurp_dir
|
||||||
slurp_file
|
slurp_file
|
||||||
|
append_to_file
|
||||||
system_or_bail
|
system_or_bail
|
||||||
system_log
|
system_log
|
||||||
run_log
|
run_log
|
||||||
@ -129,6 +130,33 @@ sub tempdir_short
|
|||||||
return File::Temp::tempdir(CLEANUP => 1);
|
return File::Temp::tempdir(CLEANUP => 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Translate a Perl file name to a host file name. Currently, this is a no-op
|
||||||
|
# except for the case of Perl=msys and host=mingw32. The subject need not
|
||||||
|
# exist, but its parent directory must exist.
|
||||||
|
sub perl2host
|
||||||
|
{
|
||||||
|
my ($subject) = @_;
|
||||||
|
return $subject unless $Config{osname} eq 'msys';
|
||||||
|
my $here = cwd;
|
||||||
|
my $leaf;
|
||||||
|
if (chdir $subject)
|
||||||
|
{
|
||||||
|
$leaf = '';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$leaf = '/' . basename $subject;
|
||||||
|
my $parent = dirname $subject;
|
||||||
|
chdir $parent or die "could not chdir \"$parent\": $!";
|
||||||
|
}
|
||||||
|
|
||||||
|
# this odd way of calling 'pwd -W' is the only way that seems to work.
|
||||||
|
my $dir = qx{sh -c "pwd -W"};
|
||||||
|
chomp $dir;
|
||||||
|
chdir $here;
|
||||||
|
return $dir . $leaf;
|
||||||
|
}
|
||||||
|
|
||||||
# Initialize a new cluster for testing.
|
# Initialize a new cluster for testing.
|
||||||
#
|
#
|
||||||
# The PGHOST environment variable is set to connect to the new cluster.
|
# The PGHOST environment variable is set to connect to the new cluster.
|
||||||
@ -257,6 +285,15 @@ sub slurp_file
|
|||||||
return $contents;
|
return $contents;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub append_to_file
|
||||||
|
{
|
||||||
|
my ($filename, $str) = @_;
|
||||||
|
open my $fh, ">>", $filename
|
||||||
|
or die "could not write \"$filename\": $!";
|
||||||
|
print $fh $str;
|
||||||
|
close $fh;
|
||||||
|
}
|
||||||
|
|
||||||
sub system_or_bail
|
sub system_or_bail
|
||||||
{
|
{
|
||||||
if (system_log(@_) != 0)
|
if (system_log(@_) != 0)
|
||||||
|
2
src/test/recovery/.gitignore
vendored
Normal file
2
src/test/recovery/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
# Generated by test suite
|
||||||
|
/tmp_check/
|
20
src/test/recovery/Makefile
Normal file
20
src/test/recovery/Makefile
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#-------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Makefile for src/test/recovery
|
||||||
|
#
|
||||||
|
# Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
|
||||||
|
# Portions Copyright (c) 1994, Regents of the University of California
|
||||||
|
#
|
||||||
|
# src/test/recovery/Makefile
|
||||||
|
#
|
||||||
|
#-------------------------------------------------------------------------
|
||||||
|
|
||||||
|
subdir = src/test/recovery
|
||||||
|
top_builddir = ../../..
|
||||||
|
include $(top_builddir)/src/Makefile.global
|
||||||
|
|
||||||
|
check:
|
||||||
|
$(prove_check)
|
||||||
|
|
||||||
|
clean distclean maintainer-clean:
|
||||||
|
rm -rf tmp_check
|
17
src/test/recovery/README
Normal file
17
src/test/recovery/README
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
src/test/recovery/README
|
||||||
|
|
||||||
|
Regression tests for recovery and replication
|
||||||
|
=============================================
|
||||||
|
|
||||||
|
This directory contains a test suite for recovery and replication.
|
||||||
|
|
||||||
|
Running the tests
|
||||||
|
=================
|
||||||
|
|
||||||
|
make check
|
||||||
|
|
||||||
|
NOTE: This creates a temporary installation, and some tests may
|
||||||
|
create one or multiple nodes, be they master or standby(s) for the
|
||||||
|
purpose of the tests.
|
||||||
|
|
||||||
|
NOTE: This requires the --enable-tap-tests argument to configure.
|
68
src/test/recovery/t/001_stream_rep.pl
Normal file
68
src/test/recovery/t/001_stream_rep.pl
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
# Minimal test testing streaming replication
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More tests => 4;
|
||||||
|
|
||||||
|
# Initialize master node
|
||||||
|
my $node_master = get_new_node('master');
|
||||||
|
$node_master->init(allows_streaming => 1);
|
||||||
|
$node_master->start;
|
||||||
|
my $backup_name = 'my_backup';
|
||||||
|
|
||||||
|
# Take backup
|
||||||
|
$node_master->backup($backup_name);
|
||||||
|
|
||||||
|
# Create streaming standby linking to master
|
||||||
|
my $node_standby_1 = get_new_node('standby_1');
|
||||||
|
$node_standby_1->init_from_backup($node_master, $backup_name,
|
||||||
|
has_streaming => 1);
|
||||||
|
$node_standby_1->start;
|
||||||
|
|
||||||
|
# Take backup of standby 1 (not mandatory, but useful to check if
|
||||||
|
# pg_basebackup works on a standby).
|
||||||
|
$node_standby_1->backup($backup_name);
|
||||||
|
|
||||||
|
# Take a second backup of the standby while the master is offline.
|
||||||
|
$node_master->stop;
|
||||||
|
$node_standby_1->backup('my_backup_2');
|
||||||
|
$node_master->start;
|
||||||
|
|
||||||
|
# Create second standby node linking to standby 1
|
||||||
|
my $node_standby_2 = get_new_node('standby_2');
|
||||||
|
$node_standby_2->init_from_backup($node_standby_1, $backup_name,
|
||||||
|
has_streaming => 1);
|
||||||
|
$node_standby_2->start;
|
||||||
|
|
||||||
|
# Create some content on master and check its presence in standby 1
|
||||||
|
$node_master->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab_int AS SELECT generate_series(1,1002) AS a");
|
||||||
|
|
||||||
|
# Wait for standbys to catch up
|
||||||
|
my $applname_1 = $node_standby_1->name;
|
||||||
|
my $applname_2 = $node_standby_2->name;
|
||||||
|
my $caughtup_query =
|
||||||
|
"SELECT pg_current_xlog_location() <= replay_location FROM pg_stat_replication WHERE application_name = '$applname_1';";
|
||||||
|
$node_master->poll_query_until('postgres', $caughtup_query)
|
||||||
|
or die "Timed out while waiting for standby 1 to catch up";
|
||||||
|
$caughtup_query =
|
||||||
|
"SELECT pg_last_xlog_replay_location() <= replay_location FROM pg_stat_replication WHERE application_name = '$applname_2';";
|
||||||
|
$node_standby_1->poll_query_until('postgres', $caughtup_query)
|
||||||
|
or die "Timed out while waiting for standby 2 to catch up";
|
||||||
|
|
||||||
|
my $result =
|
||||||
|
$node_standby_1->safe_psql('postgres', "SELECT count(*) FROM tab_int");
|
||||||
|
print "standby 1: $result\n";
|
||||||
|
is($result, qq(1002), 'check streamed content on standby 1');
|
||||||
|
|
||||||
|
$result =
|
||||||
|
$node_standby_2->safe_psql('postgres', "SELECT count(*) FROM tab_int");
|
||||||
|
print "standby 2: $result\n";
|
||||||
|
is($result, qq(1002), 'check streamed content on standby 2');
|
||||||
|
|
||||||
|
# Check that only READ-only queries can run on standbys
|
||||||
|
is($node_standby_1->psql('postgres', 'INSERT INTO tab_int VALUES (1)'),
|
||||||
|
3, 'read-only queries on standby 1');
|
||||||
|
is($node_standby_2->psql('postgres', 'INSERT INTO tab_int VALUES (1)'),
|
||||||
|
3, 'read-only queries on standby 2');
|
76
src/test/recovery/t/002_archiving.pl
Normal file
76
src/test/recovery/t/002_archiving.pl
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
# test for archiving with hot standby
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More tests => 3;
|
||||||
|
use File::Copy;
|
||||||
|
|
||||||
|
# Initialize master node, doing archives
|
||||||
|
my $node_master = get_new_node('master');
|
||||||
|
$node_master->init(
|
||||||
|
has_archiving => 1,
|
||||||
|
allows_streaming => 1);
|
||||||
|
my $backup_name = 'my_backup';
|
||||||
|
|
||||||
|
# Start it
|
||||||
|
$node_master->start;
|
||||||
|
|
||||||
|
# Take backup for slave
|
||||||
|
$node_master->backup($backup_name);
|
||||||
|
|
||||||
|
# Initialize standby node from backup, fetching WAL from archives
|
||||||
|
my $node_standby = get_new_node('standby');
|
||||||
|
$node_standby->init_from_backup($node_master, $backup_name,
|
||||||
|
has_restoring => 1);
|
||||||
|
$node_standby->append_conf(
|
||||||
|
'postgresql.conf', qq(
|
||||||
|
wal_retrieve_retry_interval = '100ms'
|
||||||
|
));
|
||||||
|
$node_standby->start;
|
||||||
|
|
||||||
|
# Create some content on master
|
||||||
|
$node_master->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a");
|
||||||
|
my $current_lsn =
|
||||||
|
$node_master->safe_psql('postgres', "SELECT pg_current_xlog_location();");
|
||||||
|
|
||||||
|
# Force archiving of WAL file to make it present on master
|
||||||
|
$node_master->safe_psql('postgres', "SELECT pg_switch_xlog()");
|
||||||
|
|
||||||
|
# Add some more content, it should not be present on standby
|
||||||
|
$node_master->safe_psql('postgres',
|
||||||
|
"INSERT INTO tab_int VALUES (generate_series(1001,2000))");
|
||||||
|
|
||||||
|
# Wait until necessary replay has been done on standby
|
||||||
|
my $caughtup_query =
|
||||||
|
"SELECT '$current_lsn'::pg_lsn <= pg_last_xlog_replay_location()";
|
||||||
|
$node_standby->poll_query_until('postgres', $caughtup_query)
|
||||||
|
or die "Timed out while waiting for standby to catch up";
|
||||||
|
|
||||||
|
my $result =
|
||||||
|
$node_standby->safe_psql('postgres', "SELECT count(*) FROM tab_int");
|
||||||
|
is($result, qq(1000), 'check content from archives');
|
||||||
|
|
||||||
|
# Check the presence of temporary files specifically generated during
|
||||||
|
# archive recovery. To ensure the presence of the temporary history
|
||||||
|
# file, switch to a timeline large enough to allow a standby to recover
|
||||||
|
# a history file from an archive. As this requires at least two timeline
|
||||||
|
# switches, promote the existing standby first. Then create a second
|
||||||
|
# standby based on the promoted one. Finally, the second standby is
|
||||||
|
# promoted.
|
||||||
|
$node_standby->promote;
|
||||||
|
|
||||||
|
my $node_standby2 = get_new_node('standby2');
|
||||||
|
$node_standby2->init_from_backup($node_master, $backup_name,
|
||||||
|
has_restoring => 1);
|
||||||
|
$node_standby2->start;
|
||||||
|
|
||||||
|
# Now promote standby2, and check that temporary files specifically
|
||||||
|
# generated during archive recovery are removed by the end of recovery.
|
||||||
|
$node_standby2->promote;
|
||||||
|
my $node_standby2_data = $node_standby2->data_dir;
|
||||||
|
ok( !-f "$node_standby2_data/pg_wal/RECOVERYHISTORY",
|
||||||
|
"RECOVERYHISTORY removed after promotion");
|
||||||
|
ok( !-f "$node_standby2_data/pg_wal/RECOVERYXLOG",
|
||||||
|
"RECOVERYXLOG removed after promotion");
|
127
src/test/recovery/t/003_recovery_targets.pl
Normal file
127
src/test/recovery/t/003_recovery_targets.pl
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# Test for recovery targets: name, timestamp, XID
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More tests => 7;
|
||||||
|
|
||||||
|
# Create and test a standby from given backup, with a certain recovery target.
|
||||||
|
# Choose $until_lsn later than the transaction commit that causes the row
|
||||||
|
# count to reach $num_rows, yet not later than the recovery target.
|
||||||
|
sub test_recovery_standby
|
||||||
|
{
|
||||||
|
my $test_name = shift;
|
||||||
|
my $node_name = shift;
|
||||||
|
my $node_master = shift;
|
||||||
|
my $recovery_params = shift;
|
||||||
|
my $num_rows = shift;
|
||||||
|
my $until_lsn = shift;
|
||||||
|
|
||||||
|
my $node_standby = get_new_node($node_name);
|
||||||
|
$node_standby->init_from_backup($node_master, 'my_backup',
|
||||||
|
has_restoring => 1);
|
||||||
|
|
||||||
|
foreach my $param_item (@$recovery_params)
|
||||||
|
{
|
||||||
|
$node_standby->append_conf(
|
||||||
|
'recovery.conf',
|
||||||
|
qq($param_item
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
$node_standby->start;
|
||||||
|
|
||||||
|
# Wait until standby has replayed enough data
|
||||||
|
my $caughtup_query =
|
||||||
|
"SELECT '$until_lsn'::pg_lsn <= pg_last_xlog_replay_location()";
|
||||||
|
$node_standby->poll_query_until('postgres', $caughtup_query)
|
||||||
|
or die "Timed out while waiting for standby to catch up";
|
||||||
|
|
||||||
|
# Create some content on master and check its presence in standby
|
||||||
|
my $result =
|
||||||
|
$node_standby->safe_psql('postgres', "SELECT count(*) FROM tab_int");
|
||||||
|
is($result, qq($num_rows), "check standby content for $test_name");
|
||||||
|
|
||||||
|
# Stop standby node
|
||||||
|
$node_standby->teardown_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Initialize master node
|
||||||
|
my $node_master = get_new_node('master');
|
||||||
|
$node_master->init(has_archiving => 1, allows_streaming => 1);
|
||||||
|
|
||||||
|
# Start it
|
||||||
|
$node_master->start;
|
||||||
|
|
||||||
|
# Create data before taking the backup, aimed at testing
|
||||||
|
# recovery_target = 'immediate'
|
||||||
|
$node_master->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a");
|
||||||
|
my $lsn1 =
|
||||||
|
$node_master->safe_psql('postgres', "SELECT pg_current_xlog_location();");
|
||||||
|
|
||||||
|
# Take backup from which all operations will be run
|
||||||
|
$node_master->backup('my_backup');
|
||||||
|
|
||||||
|
# Insert some data with used as a replay reference, with a recovery
|
||||||
|
# target TXID.
|
||||||
|
$node_master->safe_psql('postgres',
|
||||||
|
"INSERT INTO tab_int VALUES (generate_series(1001,2000))");
|
||||||
|
my $ret = $node_master->safe_psql('postgres',
|
||||||
|
"SELECT pg_current_xlog_location(), txid_current();");
|
||||||
|
my ($lsn2, $recovery_txid) = split /\|/, $ret;
|
||||||
|
|
||||||
|
# More data, with recovery target timestamp
|
||||||
|
$node_master->safe_psql('postgres',
|
||||||
|
"INSERT INTO tab_int VALUES (generate_series(2001,3000))");
|
||||||
|
my $lsn3 =
|
||||||
|
$node_master->safe_psql('postgres', "SELECT pg_current_xlog_location();");
|
||||||
|
my $recovery_time = $node_master->safe_psql('postgres', "SELECT now()");
|
||||||
|
|
||||||
|
# Even more data, this time with a recovery target name
|
||||||
|
$node_master->safe_psql('postgres',
|
||||||
|
"INSERT INTO tab_int VALUES (generate_series(3001,4000))");
|
||||||
|
my $recovery_name = "my_target";
|
||||||
|
my $lsn4 =
|
||||||
|
$node_master->safe_psql('postgres', "SELECT pg_current_xlog_location();");
|
||||||
|
$node_master->safe_psql('postgres',
|
||||||
|
"SELECT pg_create_restore_point('$recovery_name');");
|
||||||
|
|
||||||
|
# Force archiving of WAL file
|
||||||
|
$node_master->safe_psql('postgres', "SELECT pg_switch_xlog()");
|
||||||
|
|
||||||
|
# Test recovery targets
|
||||||
|
my @recovery_params = ("recovery_target = 'immediate'");
|
||||||
|
test_recovery_standby('immediate target',
|
||||||
|
'standby_1', $node_master, \@recovery_params, "1000", $lsn1);
|
||||||
|
@recovery_params = ("recovery_target_xid = '$recovery_txid'");
|
||||||
|
test_recovery_standby('XID', 'standby_2', $node_master, \@recovery_params,
|
||||||
|
"2000", $lsn2);
|
||||||
|
@recovery_params = ("recovery_target_time = '$recovery_time'");
|
||||||
|
test_recovery_standby('time', 'standby_3', $node_master, \@recovery_params,
|
||||||
|
"3000", $lsn3);
|
||||||
|
@recovery_params = ("recovery_target_name = '$recovery_name'");
|
||||||
|
test_recovery_standby('name', 'standby_4', $node_master, \@recovery_params,
|
||||||
|
"4000", $lsn4);
|
||||||
|
|
||||||
|
# Multiple targets
|
||||||
|
# Last entry has priority (note that an array respects the order of items
|
||||||
|
# not hashes).
|
||||||
|
@recovery_params = (
|
||||||
|
"recovery_target_name = '$recovery_name'",
|
||||||
|
"recovery_target_xid = '$recovery_txid'",
|
||||||
|
"recovery_target_time = '$recovery_time'");
|
||||||
|
test_recovery_standby('name + XID + time',
|
||||||
|
'standby_5', $node_master, \@recovery_params, "3000", $lsn3);
|
||||||
|
@recovery_params = (
|
||||||
|
"recovery_target_time = '$recovery_time'",
|
||||||
|
"recovery_target_name = '$recovery_name'",
|
||||||
|
"recovery_target_xid = '$recovery_txid'");
|
||||||
|
test_recovery_standby('time + name + XID',
|
||||||
|
'standby_6', $node_master, \@recovery_params, "2000", $lsn2);
|
||||||
|
@recovery_params = (
|
||||||
|
"recovery_target_xid = '$recovery_txid'",
|
||||||
|
"recovery_target_time = '$recovery_time'",
|
||||||
|
"recovery_target_name = '$recovery_name'");
|
||||||
|
test_recovery_standby('XID + time + name',
|
||||||
|
'standby_7', $node_master, \@recovery_params, "4000", $lsn4);
|
75
src/test/recovery/t/004_timeline_switch.pl
Normal file
75
src/test/recovery/t/004_timeline_switch.pl
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
# Test for timeline switch
|
||||||
|
# Ensure that a cascading standby is able to follow a newly-promoted standby
|
||||||
|
# on a new timeline.
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use File::Path qw(rmtree);
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More tests => 1;
|
||||||
|
|
||||||
|
$ENV{PGDATABASE} = 'postgres';
|
||||||
|
|
||||||
|
# Initialize master node
|
||||||
|
my $node_master = get_new_node('master');
|
||||||
|
$node_master->init(allows_streaming => 1);
|
||||||
|
$node_master->start;
|
||||||
|
|
||||||
|
# Take backup
|
||||||
|
my $backup_name = 'my_backup';
|
||||||
|
$node_master->backup($backup_name);
|
||||||
|
|
||||||
|
# Create two standbys linking to it
|
||||||
|
my $node_standby_1 = get_new_node('standby_1');
|
||||||
|
$node_standby_1->init_from_backup($node_master, $backup_name,
|
||||||
|
has_streaming => 1);
|
||||||
|
$node_standby_1->start;
|
||||||
|
my $node_standby_2 = get_new_node('standby_2');
|
||||||
|
$node_standby_2->init_from_backup($node_master, $backup_name,
|
||||||
|
has_streaming => 1);
|
||||||
|
$node_standby_2->start;
|
||||||
|
|
||||||
|
# Create some content on master
|
||||||
|
$node_master->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab_int AS SELECT generate_series(1,1000) AS a");
|
||||||
|
my $until_lsn =
|
||||||
|
$node_master->safe_psql('postgres', "SELECT pg_current_xlog_location();");
|
||||||
|
|
||||||
|
# Wait until standby has replayed enough data on standby 1
|
||||||
|
my $caughtup_query =
|
||||||
|
"SELECT '$until_lsn'::pg_lsn <= pg_last_xlog_replay_location()";
|
||||||
|
$node_standby_1->poll_query_until('postgres', $caughtup_query)
|
||||||
|
or die "Timed out while waiting for standby to catch up";
|
||||||
|
|
||||||
|
# Stop and remove master, and promote standby 1, switching it to a new timeline
|
||||||
|
$node_master->teardown_node;
|
||||||
|
$node_standby_1->promote;
|
||||||
|
|
||||||
|
# Switch standby 2 to replay from standby 1
|
||||||
|
rmtree($node_standby_2->data_dir . '/recovery.conf');
|
||||||
|
my $connstr_1 = $node_standby_1->connstr;
|
||||||
|
$node_standby_2->append_conf(
|
||||||
|
'recovery.conf', qq(
|
||||||
|
primary_conninfo='$connstr_1'
|
||||||
|
standby_mode=on
|
||||||
|
recovery_target_timeline='latest'
|
||||||
|
));
|
||||||
|
$node_standby_2->restart;
|
||||||
|
|
||||||
|
# Insert some data in standby 1 and check its presence in standby 2
|
||||||
|
# to ensure that the timeline switch has been done. Standby 1 needs
|
||||||
|
# to exit recovery first before moving on with the test.
|
||||||
|
$node_standby_1->poll_query_until('postgres',
|
||||||
|
"SELECT pg_is_in_recovery() <> true");
|
||||||
|
$node_standby_1->safe_psql('postgres',
|
||||||
|
"INSERT INTO tab_int VALUES (generate_series(1001,2000))");
|
||||||
|
$until_lsn = $node_standby_1->safe_psql('postgres',
|
||||||
|
"SELECT pg_current_xlog_location();");
|
||||||
|
$caughtup_query =
|
||||||
|
"SELECT '$until_lsn'::pg_lsn <= pg_last_xlog_replay_location()";
|
||||||
|
$node_standby_2->poll_query_until('postgres', $caughtup_query)
|
||||||
|
or die "Timed out while waiting for standby to catch up";
|
||||||
|
|
||||||
|
my $result =
|
||||||
|
$node_standby_2->safe_psql('postgres', "SELECT count(*) FROM tab_int");
|
||||||
|
is($result, qq(2000), 'check content of standby 2');
|
69
src/test/recovery/t/005_replay_delay.pl
Normal file
69
src/test/recovery/t/005_replay_delay.pl
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
# Checks for recovery_min_apply_delay
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More tests => 1;
|
||||||
|
|
||||||
|
# Initialize master node
|
||||||
|
my $node_master = get_new_node('master');
|
||||||
|
$node_master->init(allows_streaming => 1);
|
||||||
|
$node_master->start;
|
||||||
|
|
||||||
|
# And some content
|
||||||
|
$node_master->safe_psql('postgres',
|
||||||
|
"CREATE TABLE tab_int AS SELECT generate_series(1, 10) AS a");
|
||||||
|
|
||||||
|
# Take backup
|
||||||
|
my $backup_name = 'my_backup';
|
||||||
|
$node_master->backup($backup_name);
|
||||||
|
|
||||||
|
# Create streaming standby from backup
|
||||||
|
my $node_standby = get_new_node('standby');
|
||||||
|
my $delay = 3;
|
||||||
|
$node_standby->init_from_backup($node_master, $backup_name,
|
||||||
|
has_streaming => 1);
|
||||||
|
$node_standby->append_conf(
|
||||||
|
'recovery.conf', qq(
|
||||||
|
recovery_min_apply_delay = '${delay}s'
|
||||||
|
));
|
||||||
|
$node_standby->start;
|
||||||
|
|
||||||
|
# Make new content on master and check its presence in standby depending
|
||||||
|
# on the delay applied above. Before doing the insertion, get the
|
||||||
|
# current timestamp that will be used as a comparison base. Even on slow
|
||||||
|
# machines, this allows to have a predictable behavior when comparing the
|
||||||
|
# delay between data insertion moment on master and replay time on standby.
|
||||||
|
my $master_insert_time = time();
|
||||||
|
$node_master->safe_psql('postgres',
|
||||||
|
"INSERT INTO tab_int VALUES (generate_series(11, 20))");
|
||||||
|
|
||||||
|
# Now wait for replay to complete on standby. We're done waiting when the
|
||||||
|
# slave has replayed up to the previously saved master LSN.
|
||||||
|
my $until_lsn =
|
||||||
|
$node_master->safe_psql('postgres', "SELECT pg_current_xlog_location()");
|
||||||
|
|
||||||
|
my $remaining = 90;
|
||||||
|
while ($remaining-- > 0)
|
||||||
|
{
|
||||||
|
|
||||||
|
# Done waiting?
|
||||||
|
my $replay_status = $node_standby->safe_psql('postgres',
|
||||||
|
"SELECT (pg_last_xlog_replay_location() - '$until_lsn'::pg_lsn) >= 0"
|
||||||
|
);
|
||||||
|
last if $replay_status eq 't';
|
||||||
|
|
||||||
|
# No, sleep some more.
|
||||||
|
my $sleep = $master_insert_time + $delay - time();
|
||||||
|
$sleep = 1 if $sleep < 1;
|
||||||
|
sleep $sleep;
|
||||||
|
}
|
||||||
|
|
||||||
|
die "Maximum number of attempts reached ($remaining remain)"
|
||||||
|
if $remaining < 0;
|
||||||
|
|
||||||
|
# This test is successful if and only if the LSN has been applied with at least
|
||||||
|
# the configured apply delay.
|
||||||
|
ok(time() - $master_insert_time >= $delay,
|
||||||
|
"standby applies WAL only after replication delay");
|
93
src/test/recovery/t/008_fsm_truncation.pl
Normal file
93
src/test/recovery/t/008_fsm_truncation.pl
Normal file
@ -0,0 +1,93 @@
|
|||||||
|
# Test WAL replay of FSM changes.
|
||||||
|
#
|
||||||
|
# FSM changes don't normally need to be WAL-logged, except for truncation.
|
||||||
|
# The FSM mustn't return a page that doesn't exist (anymore).
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
|
||||||
|
use PostgresNode;
|
||||||
|
use TestLib;
|
||||||
|
use Test::More tests => 1;
|
||||||
|
|
||||||
|
my $node_master = get_new_node('master');
|
||||||
|
$node_master->init(allows_streaming => 1);
|
||||||
|
|
||||||
|
$node_master->append_conf('postgresql.conf', qq{
|
||||||
|
fsync = on
|
||||||
|
wal_level = hot_standby
|
||||||
|
wal_log_hints = on
|
||||||
|
max_prepared_transactions = 5
|
||||||
|
autovacuum = off
|
||||||
|
});
|
||||||
|
|
||||||
|
# Create a master node and its standby, initializing both with some data
|
||||||
|
# at the same time.
|
||||||
|
$node_master->start;
|
||||||
|
|
||||||
|
$node_master->backup('master_backup');
|
||||||
|
my $node_standby = get_new_node('standby');
|
||||||
|
$node_standby->init_from_backup($node_master, 'master_backup',
|
||||||
|
has_streaming => 1);
|
||||||
|
$node_standby->start;
|
||||||
|
|
||||||
|
$node_master->psql('postgres', qq{
|
||||||
|
create table testtab (a int, b char(100));
|
||||||
|
insert into testtab select generate_series(1,1000), 'foo';
|
||||||
|
insert into testtab select generate_series(1,1000), 'foo';
|
||||||
|
delete from testtab where ctid > '(8,0)';
|
||||||
|
});
|
||||||
|
|
||||||
|
# Take a lock on the table to prevent following vacuum from truncating it
|
||||||
|
$node_master->psql('postgres', qq{
|
||||||
|
begin;
|
||||||
|
lock table testtab in row share mode;
|
||||||
|
prepare transaction 'p1';
|
||||||
|
});
|
||||||
|
|
||||||
|
# Vacuum, update FSM without truncation
|
||||||
|
$node_master->psql('postgres', 'vacuum verbose testtab');
|
||||||
|
|
||||||
|
# Force a checkpoint
|
||||||
|
$node_master->psql('postgres', 'checkpoint');
|
||||||
|
|
||||||
|
# Now do some more insert/deletes, another vacuum to ensure full-page writes
|
||||||
|
# are done
|
||||||
|
$node_master->psql('postgres', qq{
|
||||||
|
insert into testtab select generate_series(1,1000), 'foo';
|
||||||
|
delete from testtab where ctid > '(8,0)';
|
||||||
|
vacuum verbose testtab;
|
||||||
|
});
|
||||||
|
|
||||||
|
# Ensure all buffers are now clean on the standby
|
||||||
|
$node_standby->psql('postgres', 'checkpoint');
|
||||||
|
|
||||||
|
# Release the lock, vacuum again which should lead to truncation
|
||||||
|
$node_master->psql('postgres', qq{
|
||||||
|
rollback prepared 'p1';
|
||||||
|
vacuum verbose testtab;
|
||||||
|
});
|
||||||
|
|
||||||
|
$node_master->psql('postgres', 'checkpoint');
|
||||||
|
my $until_lsn =
|
||||||
|
$node_master->safe_psql('postgres', "SELECT pg_current_xlog_location();");
|
||||||
|
|
||||||
|
# Wait long enough for standby to receive and apply all WAL
|
||||||
|
my $caughtup_query =
|
||||||
|
"SELECT '$until_lsn'::pg_lsn <= pg_last_xlog_replay_location()";
|
||||||
|
$node_standby->poll_query_until('postgres', $caughtup_query)
|
||||||
|
or die "Timed out while waiting for standby to catch up";
|
||||||
|
|
||||||
|
# Promote the standby
|
||||||
|
$node_standby->promote;
|
||||||
|
$node_standby->poll_query_until('postgres',
|
||||||
|
"SELECT NOT pg_is_in_recovery()")
|
||||||
|
or die "Timed out while waiting for promotion of standby";
|
||||||
|
$node_standby->psql('postgres', 'checkpoint');
|
||||||
|
|
||||||
|
# Restart to discard in-memory copy of FSM
|
||||||
|
$node_standby->restart;
|
||||||
|
|
||||||
|
# Insert should work on standby
|
||||||
|
is($node_standby->psql('postgres',
|
||||||
|
qq{insert into testtab select generate_series(1,1000), 'foo';}),
|
||||||
|
0, 'INSERT succeeds with truncated relation FSM');
|
204
src/test/recovery/t/017_shm.pl
Normal file
204
src/test/recovery/t/017_shm.pl
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
#
|
||||||
|
# Tests of pg_shmem.h functions
|
||||||
|
#
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use IPC::Run 'run';
|
||||||
|
use PostgresNode;
|
||||||
|
use Test::More;
|
||||||
|
use TestLib;
|
||||||
|
use Time::HiRes qw(usleep);
|
||||||
|
|
||||||
|
if ($windows_os)
|
||||||
|
{
|
||||||
|
plan skip_all => 'SysV shared memory not supported by this platform';
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
plan tests => 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
my $tempdir = TestLib::tempdir;
|
||||||
|
my $port;
|
||||||
|
|
||||||
|
# Log "ipcs" diffs on a best-effort basis, swallowing any error.
|
||||||
|
my $ipcs_before = "$tempdir/ipcs_before";
|
||||||
|
eval { run_log [ 'ipcs', '-am' ], '>', $ipcs_before; };
|
||||||
|
|
||||||
|
sub log_ipcs
|
||||||
|
{
|
||||||
|
eval { run_log [ 'ipcs', '-am' ], '|', [ 'diff', $ipcs_before, '-' ] };
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
# These tests need a $port such that nothing creates or removes a segment in
|
||||||
|
# $port's IpcMemoryKey range while this test script runs. While there's no
|
||||||
|
# way to ensure that in general, we do ensure that if PostgreSQL tests are the
|
||||||
|
# only actors. With TCP, the first get_new_node picks a port number. With
|
||||||
|
# Unix sockets, use a postmaster, $port_holder, to represent a key space
|
||||||
|
# reservation. $port_holder holds a reservation on the key space of port
|
||||||
|
# 1+$port_holder->port if it created the first IpcMemoryKey of its own port's
|
||||||
|
# key space. If multiple copies of this test script run concurrently, they
|
||||||
|
# will pick different ports. $port_holder postmasters use odd-numbered ports,
|
||||||
|
# and tests use even-numbered ports. In the absence of collisions from other
|
||||||
|
# shmget() activity, gnat starts with key 0x7d001 (512001), and flea starts
|
||||||
|
# with key 0x7d002 (512002).
|
||||||
|
my $port_holder;
|
||||||
|
if (!$PostgresNode::use_tcp)
|
||||||
|
{
|
||||||
|
my $lock_port;
|
||||||
|
for ($lock_port = 511; $lock_port < 711; $lock_port += 2)
|
||||||
|
{
|
||||||
|
$port_holder = PostgresNode->get_new_node(
|
||||||
|
"port${lock_port}_holder",
|
||||||
|
port => $lock_port,
|
||||||
|
own_host => 1);
|
||||||
|
$port_holder->init(hba_permit_replication => 0);
|
||||||
|
$port_holder->append_conf('postgresql.conf', 'max_connections = 5');
|
||||||
|
$port_holder->start;
|
||||||
|
# Match the AddToDataDirLockFile() call in sysv_shmem.c. Assume all
|
||||||
|
# systems not using sysv_shmem.c do use TCP.
|
||||||
|
my $shmem_key_line_prefix = sprintf("%9lu ", 1 + $lock_port * 1000);
|
||||||
|
last
|
||||||
|
if slurp_file($port_holder->data_dir . '/postmaster.pid') =~
|
||||||
|
/^$shmem_key_line_prefix/m;
|
||||||
|
$port_holder->stop;
|
||||||
|
}
|
||||||
|
$port = $lock_port + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Node setup.
|
||||||
|
sub init_start
|
||||||
|
{
|
||||||
|
my $name = shift;
|
||||||
|
my $ret = PostgresNode->get_new_node($name, port => $port, own_host => 1);
|
||||||
|
defined($port) or $port = $ret->port; # same port for all nodes
|
||||||
|
$ret->init(hba_permit_replication => 0);
|
||||||
|
# Limit semaphore consumption, since we run several nodes concurrently.
|
||||||
|
$ret->append_conf('postgresql.conf', 'max_connections = 5');
|
||||||
|
$ret->start;
|
||||||
|
log_ipcs();
|
||||||
|
return $ret;
|
||||||
|
}
|
||||||
|
my $gnat = init_start 'gnat';
|
||||||
|
my $flea = init_start 'flea';
|
||||||
|
|
||||||
|
# Upon postmaster death, postmaster children exit automatically.
|
||||||
|
$gnat->kill9;
|
||||||
|
log_ipcs();
|
||||||
|
$flea->restart; # flea ignores the shm key gnat abandoned.
|
||||||
|
log_ipcs();
|
||||||
|
poll_start($gnat); # gnat recycles its former shm key.
|
||||||
|
log_ipcs();
|
||||||
|
|
||||||
|
# After clean shutdown, the nodes swap shm keys.
|
||||||
|
$gnat->stop;
|
||||||
|
$flea->restart;
|
||||||
|
log_ipcs();
|
||||||
|
$gnat->start;
|
||||||
|
log_ipcs();
|
||||||
|
|
||||||
|
# Scenarios involving no postmaster.pid, dead postmaster, and a live backend.
|
||||||
|
# Use a regress.c function to emulate the responsiveness of a backend working
|
||||||
|
# through a CPU-intensive task.
|
||||||
|
$gnat->safe_psql('postgres', <<EOSQL);
|
||||||
|
CREATE FUNCTION wait_pid(int)
|
||||||
|
RETURNS void
|
||||||
|
AS '$ENV{REGRESS_SHLIB}'
|
||||||
|
LANGUAGE C STRICT;
|
||||||
|
EOSQL
|
||||||
|
my $slow_query = 'SELECT wait_pid(pg_backend_pid())';
|
||||||
|
my ($stdout, $stderr);
|
||||||
|
my $slow_client = IPC::Run::start(
|
||||||
|
[
|
||||||
|
'psql', '-X', '-qAt', '-d', $gnat->connstr('postgres'),
|
||||||
|
'-c', $slow_query
|
||||||
|
],
|
||||||
|
'<',
|
||||||
|
\undef,
|
||||||
|
'>',
|
||||||
|
\$stdout,
|
||||||
|
'2>',
|
||||||
|
\$stderr,
|
||||||
|
IPC::Run::timeout(900)); # five times the poll_query_until timeout
|
||||||
|
ok( $gnat->poll_query_until(
|
||||||
|
'postgres',
|
||||||
|
"SELECT true FROM pg_stat_activity WHERE query = '$slow_query'"),
|
||||||
|
'slow query started');
|
||||||
|
my $slow_pid = $gnat->safe_psql('postgres',
|
||||||
|
"SELECT pid FROM pg_stat_activity WHERE query = '$slow_query'");
|
||||||
|
$gnat->kill9;
|
||||||
|
unlink($gnat->data_dir . '/postmaster.pid');
|
||||||
|
$gnat->rotate_logfile;
|
||||||
|
log_ipcs();
|
||||||
|
# Reject ordinary startup. Retry for the same reasons poll_start() does.
|
||||||
|
my $pre_existing_msg = qr/pre-existing shared memory block/;
|
||||||
|
{
|
||||||
|
my $max_attempts = 180 * 10; # Retry every 0.1s for at least 180s.
|
||||||
|
my $attempts = 0;
|
||||||
|
while ($attempts < $max_attempts)
|
||||||
|
{
|
||||||
|
last
|
||||||
|
if $gnat->start(fail_ok => 1)
|
||||||
|
|| slurp_file($gnat->logfile) =~ $pre_existing_msg;
|
||||||
|
usleep(100_000);
|
||||||
|
$attempts++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
like(slurp_file($gnat->logfile),
|
||||||
|
$pre_existing_msg, 'detected live backend via shared memory');
|
||||||
|
# Reject single-user startup.
|
||||||
|
my $single_stderr;
|
||||||
|
ok( !run_log(
|
||||||
|
[ 'postgres', '--single', '-D', $gnat->data_dir, 'template1' ],
|
||||||
|
'<', \undef, '2>', \$single_stderr),
|
||||||
|
'live query blocks --single');
|
||||||
|
print STDERR $single_stderr;
|
||||||
|
like($single_stderr, $pre_existing_msg,
|
||||||
|
'single-user mode detected live backend via shared memory');
|
||||||
|
log_ipcs();
|
||||||
|
# Fail to reject startup if shm key N has become available and we crash while
|
||||||
|
# using key N+1. This is unwanted, but expected.
|
||||||
|
$flea->stop; # release first key
|
||||||
|
is($gnat->start(fail_ok => 1), 1, 'key turnover fools only sysv_shmem.c');
|
||||||
|
$gnat->stop; # release first key
|
||||||
|
$flea->start; # grab first key
|
||||||
|
# cleanup
|
||||||
|
TestLib::system_log('pg_ctl', 'kill', 'QUIT', $slow_pid);
|
||||||
|
$slow_client->finish; # client has detected backend termination
|
||||||
|
log_ipcs();
|
||||||
|
poll_start($gnat); # recycle second key
|
||||||
|
|
||||||
|
$gnat->stop;
|
||||||
|
$flea->stop;
|
||||||
|
$port_holder->stop if $port_holder;
|
||||||
|
log_ipcs();
|
||||||
|
|
||||||
|
|
||||||
|
# We may need retries to start a new postmaster. Causes:
|
||||||
|
# - kernel is slow to deliver SIGKILL
|
||||||
|
# - postmaster parent is slow to waitpid()
|
||||||
|
# - postmaster child is slow to exit in response to SIGQUIT
|
||||||
|
# - postmaster child is slow to exit after postmaster death
|
||||||
|
sub poll_start
|
||||||
|
{
|
||||||
|
my ($node) = @_;
|
||||||
|
|
||||||
|
my $max_attempts = 180 * 10;
|
||||||
|
my $attempts = 0;
|
||||||
|
|
||||||
|
while ($attempts < $max_attempts)
|
||||||
|
{
|
||||||
|
$node->start(fail_ok => 1) && return 1;
|
||||||
|
|
||||||
|
# Wait 0.1 second before retrying.
|
||||||
|
usleep(100_000);
|
||||||
|
|
||||||
|
$attempts++;
|
||||||
|
}
|
||||||
|
|
||||||
|
# No success within 180 seconds. Try one last time without fail_ok, which
|
||||||
|
# will BAIL_OUT unless it succeeds.
|
||||||
|
$node->start && return 1;
|
||||||
|
return 0;
|
||||||
|
}
|
@ -37,7 +37,7 @@ if (-e "src/tools/msvc/buildenv.pl")
|
|||||||
|
|
||||||
my $what = shift || "";
|
my $what = shift || "";
|
||||||
if ($what =~
|
if ($what =~
|
||||||
/^(check|installcheck|plcheck|contribcheck|modulescheck|ecpgcheck|isolationcheck|upgradecheck|bincheck|taptest)$/i
|
/^(check|installcheck|plcheck|contribcheck|modulescheck|ecpgcheck|isolationcheck|upgradecheck|bincheck|recoverycheck|taptest)$/i
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
$what = uc $what;
|
$what = uc $what;
|
||||||
@ -85,6 +85,7 @@ my %command = (
|
|||||||
MODULESCHECK => \&modulescheck,
|
MODULESCHECK => \&modulescheck,
|
||||||
ISOLATIONCHECK => \&isolationcheck,
|
ISOLATIONCHECK => \&isolationcheck,
|
||||||
BINCHECK => \&bincheck,
|
BINCHECK => \&bincheck,
|
||||||
|
RECOVERYCHECK => \&recoverycheck,
|
||||||
UPGRADECHECK => \&upgradecheck,
|
UPGRADECHECK => \&upgradecheck,
|
||||||
TAPTEST => \&taptest,);
|
TAPTEST => \&taptest,);
|
||||||
|
|
||||||
@ -203,8 +204,9 @@ sub tap_check
|
|||||||
|
|
||||||
# adjust the environment for just this test
|
# adjust the environment for just this test
|
||||||
local %ENV = %ENV;
|
local %ENV = %ENV;
|
||||||
$ENV{PERL5LIB} = "$topdir/src/test/perl;$ENV{PERL5LIB}";
|
$ENV{PERL5LIB} = "$topdir/src/test/perl;$ENV{PERL5LIB}";
|
||||||
$ENV{PG_REGRESS} = "$topdir/$Config/pg_regress/pg_regress";
|
$ENV{PG_REGRESS} = "$topdir/$Config/pg_regress/pg_regress";
|
||||||
|
$ENV{REGRESS_SHLIB} = "$topdir/src/test/regress/regress.dll";
|
||||||
|
|
||||||
$ENV{TESTDIR} = "$dir";
|
$ENV{TESTDIR} = "$dir";
|
||||||
|
|
||||||
@ -439,6 +441,16 @@ sub modulescheck
|
|||||||
exit $mstat if $mstat;
|
exit $mstat if $mstat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sub recoverycheck
|
||||||
|
{
|
||||||
|
InstallTemp();
|
||||||
|
|
||||||
|
my $mstat = 0;
|
||||||
|
my $dir = "$topdir/src/test/recovery";
|
||||||
|
my $status = tap_check($dir);
|
||||||
|
exit $status if $status;
|
||||||
|
}
|
||||||
|
|
||||||
# Run "initdb", then reconfigure authentication.
|
# Run "initdb", then reconfigure authentication.
|
||||||
sub standard_initdb
|
sub standard_initdb
|
||||||
{
|
{
|
||||||
@ -683,6 +695,7 @@ sub usage
|
|||||||
" isolationcheck run isolation tests\n",
|
" isolationcheck run isolation tests\n",
|
||||||
" modulescheck run tests of modules in src/test/modules/\n",
|
" modulescheck run tests of modules in src/test/modules/\n",
|
||||||
" plcheck run tests of PL languages\n",
|
" plcheck run tests of PL languages\n",
|
||||||
|
" recoverycheck run recovery test suite\n",
|
||||||
" taptest run an arbitrary TAP test set\n",
|
" taptest run an arbitrary TAP test set\n",
|
||||||
" upgradecheck run tests of pg_upgrade\n",
|
" upgradecheck run tests of pg_upgrade\n",
|
||||||
"\nOptions for <arg>: (used by check and installcheck)\n",
|
"\nOptions for <arg>: (used by check and installcheck)\n",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user