diff options
author | Noah Misch <noah@leadboat.com> | 2019-04-03 17:03:46 -0700 |
---|---|---|
committer | Noah Misch <noah@leadboat.com> | 2019-04-03 17:03:46 -0700 |
commit | 2f932f71d9f2963bbd201129d7b971c8f5f077fd (patch) | |
tree | a9647622166d61e3b7668eca618f37c47d864f5f /src/test/perl/PostgresNode.pm | |
parent | af052daec5aa282eab2d22fa3b24d6109a30c2f3 (diff) | |
download | postgresql-2f932f71d9f2963bbd201129d7b971c8f5f077fd.tar.gz postgresql-2f932f71d9f2963bbd201129d7b971c8f5f077fd.zip |
Consistently test for in-use shared memory.
postmaster startup scrutinizes any shared memory segment recorded in
postmaster.pid, exiting if that segment matches the current data
directory and has an attached process. When the postmaster.pid file was
missing, a starting postmaster used weaker checks. Change to use the
same checks in both scenarios. This increases the chance of a startup
failure, in lieu of data corruption, if the DBA does "kill -9 `head -n1
postmaster.pid` && rm postmaster.pid && pg_ctl -w start". A postmaster
will no longer recycle segments pertaining to other data directories.
That's good for production, but it's bad for integration tests that
crash a postmaster and immediately delete its data directory. Such a
test now leaks a segment indefinitely. No "make check-world" test does
that. win32_shmem.c already avoided all these problems. In 9.6 and
later, enhance PostgresNode to facilitate testing. Back-patch to 9.4
(all supported versions).
Reviewed by Daniel Gustafsson and Kyotaro HORIGUCHI.
Discussion: https://postgr.es/m/20130911033341.GD225735@tornado.leadboat.com
Diffstat (limited to 'src/test/perl/PostgresNode.pm')
-rw-r--r-- | src/test/perl/PostgresNode.pm | 184 |
1 files changed, 147 insertions, 37 deletions
diff --git a/src/test/perl/PostgresNode.pm b/src/test/perl/PostgresNode.pm index 81deed98430..f6570639e00 100644 --- a/src/test/perl/PostgresNode.pm +++ b/src/test/perl/PostgresNode.pm @@ -104,7 +104,8 @@ our @EXPORT = qw( get_new_node ); -our ($test_localhost, $test_pghost, $last_port_assigned, @all_nodes, $died); +our ($use_tcp, $test_localhost, $test_pghost, $last_host_assigned, + $last_port_assigned, @all_nodes, $died); # Windows path to virtual file system root @@ -118,13 +119,14 @@ if ($Config{osname} eq 'msys') INIT { - # PGHOST is set once and for all through a single series of tests when - # this module is loaded. - $test_localhost = "127.0.0.1"; - $test_pghost = - $TestLib::windows_os ? $test_localhost : TestLib::tempdir_short; - $ENV{PGHOST} = $test_pghost; - $ENV{PGDATABASE} = 'postgres'; + # Set PGHOST for backward compatibility. This doesn't work for own_host + # nodes, so prefer to not rely on this when writing new tests. + $use_tcp = $TestLib::windows_os; + $test_localhost = "127.0.0.1"; + $last_host_assigned = 1; + $test_pghost = $use_tcp ? $test_localhost : TestLib::tempdir_short; + $ENV{PGHOST} = $test_pghost; + $ENV{PGDATABASE} = 'postgres'; # Tracking of last port value assigned to accelerate free port lookup. $last_port_assigned = int(rand() * 16384) + 49152; @@ -155,7 +157,9 @@ sub new _host => $pghost, _basedir => "$TestLib::tmp_check/t_${testname}_${name}_data", _name => $name, - _logfile => "$TestLib::log_path/${testname}_${name}.log" + _logfile_generation => 0, + _logfile_base => "$TestLib::log_path/${testname}_${name}", + _logfile => "$TestLib::log_path/${testname}_${name}.log" }; bless $self, $class; @@ -473,8 +477,9 @@ sub init print $conf "max_wal_senders = 0\n"; } - if ($TestLib::windows_os) + if ($use_tcp) { + print $conf "unix_socket_directories = ''\n"; print $conf "listen_addresses = '$host'\n"; } else @@ -536,12 +541,11 @@ sub backup { my ($self, $backup_name) = @_; my $backup_path = $self->backup_dir . '/' . $backup_name; - my $port = $self->port; my $name = $self->name; print "# Taking pg_basebackup $backup_name from node \"$name\"\n"; - TestLib::system_or_bail('pg_basebackup', '-D', $backup_path, '-p', $port, - '--no-sync'); + TestLib::system_or_bail('pg_basebackup', '-D', $backup_path, '-h', + $self->host, '-p', $self->port, '--no-sync'); print "# Backup finished\n"; return; } @@ -651,6 +655,7 @@ sub init_from_backup { my ($self, $root_node, $backup_name, %params) = @_; my $backup_path = $root_node->backup_dir . '/' . $backup_name; + my $host = $self->host; my $port = $self->port; my $node_name = $self->name; my $root_name = $root_node->name; @@ -677,6 +682,15 @@ sub init_from_backup qq( port = $port )); + if ($use_tcp) + { + $self->append_conf('postgresql.conf', "listen_addresses = '$host'"); + } + else + { + $self->append_conf('postgresql.conf', + "unix_socket_directories = '$host'"); + } $self->enable_streaming($root_node) if $params{has_streaming}; $self->enable_restoring($root_node) if $params{has_restoring}; return; @@ -684,17 +698,45 @@ port = $port =pod -=item $node->start() +=item $node->rotate_logfile() + +Switch to a new PostgreSQL log file. This does not alter any running +PostgreSQL process. Subsequent method calls, including pg_ctl invocations, +will use the new name. Return the new name. + +=cut + +sub rotate_logfile +{ + my ($self) = @_; + $self->{_logfile} = sprintf('%s_%d.log', + $self->{_logfile_base}, + ++$self->{_logfile_generation}); + return $self->{_logfile}; +} + +=pod + +=item $node->start(%params) => success_or_failure Wrapper for pg_ctl start Start the node and wait until it is ready to accept connections. +=over + +=item fail_ok => 1 + +By default, failure terminates the entire F<prove> invocation. If given, +instead return a true or false value to indicate success or failure. + +=back + =cut sub start { - my ($self) = @_; + my ($self, %params) = @_; my $port = $self->port; my $pgdata = $self->data_dir; my $name = $self->name; @@ -721,10 +763,34 @@ sub start { print "# pg_ctl start failed; logfile:\n"; print TestLib::slurp_file($self->logfile); - BAIL_OUT("pg_ctl start failed"); + BAIL_OUT("pg_ctl start failed") unless $params{fail_ok}; + return 0; } $self->_update_pid(1); + return 1; +} + +=pod + +=item $node->kill9() + +Send SIGKILL (signal 9) to the postmaster. + +Note: if the node is already known stopped, this does nothing. +However, if we think it's running and it's not, it's important for +this to fail. Otherwise, tests might fail to detect server crashes. + +=cut + +sub kill9 +{ + my ($self) = @_; + my $name = $self->name; + return unless defined $self->{_pid}; + print "### Killing node \"$name\" using signal 9\n"; + kill(9, $self->{_pid}) or BAIL_OUT("kill(9, $self->{_pid}) failed"); + $self->{_pid} = undef; return; } @@ -965,7 +1031,7 @@ sub _update_pid =pod -=item PostgresNode->get_new_node(node_name) +=item PostgresNode->get_new_node(node_name, %params) Build a new object of class C<PostgresNode> (or of a subclass, if you have one), assigning a free port number. Remembers the node, to prevent its port @@ -974,6 +1040,22 @@ shut down when the test script exits. You should generally use this instead of C<PostgresNode::new(...)>. +=over + +=item port => [1,65535] + +By default, this function assigns a port number to each node. Specify this to +force a particular port number. The caller is responsible for evaluating +potential conflicts and privilege requirements. + +=item own_host => 1 + +By default, all nodes use the same PGHOST value. If specified, generate a +PGHOST specific to this node. This allows multiple nodes to use the same +port. + +=back + For backwards compatibility, it is also exported as a standalone function, which can only create objects of class C<PostgresNode>. @@ -982,10 +1064,11 @@ which can only create objects of class C<PostgresNode>. sub get_new_node { my $class = 'PostgresNode'; - $class = shift if 1 < scalar @_; - my $name = shift; - my $found = 0; - my $port = $last_port_assigned; + $class = shift if scalar(@_) % 2 != 1; + my ($name, %params) = @_; + my $port_is_forced = defined $params{port}; + my $found = $port_is_forced; + my $port = $port_is_forced ? $params{port} : $last_port_assigned; while ($found == 0) { @@ -1002,13 +1085,15 @@ sub get_new_node $found = 0 if ($node->port == $port); } - # Check to see if anything else is listening on this TCP port. - # This is *necessary* on Windows, and seems like a good idea - # on Unixen as well, even though we don't ask the postmaster - # to open a TCP port on Unix. + # Check to see if anything else is listening on this TCP port. Accept + # only ports available for all possible listen_addresses values, so + # the caller can harness this port for the widest range of purposes. + # This is *necessary* on Windows, and seems like a good idea on Unixen + # as well, even though we don't ask the postmaster to open a TCP port + # on Unix. if ($found == 1) { - my $iaddr = inet_aton($test_localhost); + my $iaddr = inet_aton('0.0.0.0'); my $paddr = sockaddr_in($port, $iaddr); my $proto = getprotobyname("tcp"); @@ -1024,16 +1109,35 @@ sub get_new_node } } - print "# Found free port $port\n"; + print "# Found port $port\n"; + + # Select a host. + my $host = $test_pghost; + if ($params{own_host}) + { + if ($use_tcp) + { + # This assumes $use_tcp platforms treat every address in + # 127.0.0.1/24, not just 127.0.0.1, as a usable loopback. + $last_host_assigned++; + $last_host_assigned > 254 and BAIL_OUT("too many own_host nodes"); + $host = '127.0.0.' . $last_host_assigned; + } + else + { + $host = "$test_pghost/$name"; # Assume $name =~ /^[-_a-zA-Z0-9]+$/ + mkdir $host; + } + } # Lock port number found by creating a new node - my $node = $class->new($name, $test_pghost, $port); + my $node = $class->new($name, $host, $port); # Add node to list of nodes push(@all_nodes, $node); # And update port for next time - $last_port_assigned = $port; + $port_is_forced or $last_port_assigned = $port; return $node; } @@ -1424,9 +1528,8 @@ $stderr); =item $node->command_ok(...) -Runs a shell command like TestLib::command_ok, but with PGPORT -set so that the command will default to connecting to this -PostgresNode. +Runs a shell command like TestLib::command_ok, but with PGHOST and PGPORT set +so that the command will default to connecting to this PostgresNode. =cut @@ -1436,6 +1539,7 @@ sub command_ok my $self = shift; + local $ENV{PGHOST} = $self->host; local $ENV{PGPORT} = $self->port; TestLib::command_ok(@_); @@ -1446,7 +1550,7 @@ sub command_ok =item $node->command_fails(...) -TestLib::command_fails with our PGPORT. See command_ok(...) +TestLib::command_fails with our connection parameters. See command_ok(...) =cut @@ -1456,6 +1560,7 @@ sub command_fails my $self = shift; + local $ENV{PGHOST} = $self->host; local $ENV{PGPORT} = $self->port; TestLib::command_fails(@_); @@ -1466,7 +1571,7 @@ sub command_fails =item $node->command_like(...) -TestLib::command_like with our PGPORT. See command_ok(...) +TestLib::command_like with our connection parameters. See command_ok(...) =cut @@ -1476,6 +1581,7 @@ sub command_like my $self = shift; + local $ENV{PGHOST} = $self->host; local $ENV{PGPORT} = $self->port; TestLib::command_like(@_); @@ -1486,7 +1592,8 @@ sub command_like =item $node->command_checks_all(...) -TestLib::command_checks_all with our PGPORT. See command_ok(...) +TestLib::command_checks_all with our connection parameters. See +command_ok(...) =cut @@ -1496,6 +1603,7 @@ sub command_checks_all my $self = shift; + local $ENV{PGHOST} = $self->host; local $ENV{PGPORT} = $self->port; TestLib::command_checks_all(@_); @@ -1520,6 +1628,7 @@ sub issues_sql_like my ($self, $cmd, $expected_sql, $test_name) = @_; + local $ENV{PGHOST} = $self->host; local $ENV{PGPORT} = $self->port; truncate $self->logfile, 0; @@ -1534,8 +1643,8 @@ sub issues_sql_like =item $node->run_log(...) -Runs a shell command like TestLib::run_log, but with PGPORT set so -that the command will default to connecting to this PostgresNode. +Runs a shell command like TestLib::run_log, but with connection parameters set +so that the command will default to connecting to this PostgresNode. =cut @@ -1543,6 +1652,7 @@ sub run_log { my $self = shift; + local $ENV{PGHOST} = $self->host; local $ENV{PGPORT} = $self->port; TestLib::run_log(@_); |