#!/usr/bin/env perl
# test if reserved connections for replication works
#
# For the test we will create a master instance with the connection limit of
# 15, 3 reserved superuser connections and 2 max_wal_senders.
# The max_wal_senders should be able to connect when no normal user can connect
# anymore. We should have 10 normal connections.
#
# This script uses ports 10001 to 10004.

use strict;
use warnings;
use DBI;
use File::Path qw(remove_tree);
use File::ReadBackwards;

use Env qw(BINDIR ROOTDIR);

$BINDIR='./bin/' unless $BINDIR;
$ROOTDIR='./' unless $ROOTDIR;

my $success = 'success';

print("using the following directories:\n");
printf("bindir: %s\n", $BINDIR);
printf("rootdir: %s\n", $ROOTDIR);

my $PG_BASEBACKUP="${BINDIR}/pg_basebackup";
my $PG_CTL="${BINDIR}/pg_ctl -w";

# delete old log files
unlink("${ROOTDIR}/main1.log");
unlink("${ROOTDIR}/slave1.log");
unlink("${ROOTDIR}/slave2.log");
unlink("${ROOTDIR}/slave3.log");

# init the main cluster
system("$PG_CTL initdb -D ${ROOTDIR}/main1");

# write the postgres.conf
open(PGCONF, ">>${ROOTDIR}/main1/postgresql.conf");
print PGCONF "wal_level=hot_standby\n";
print PGCONF "archive_mode=on\n";
print PGCONF "archive_command='true'\n";
print PGCONF "wal_keep_segments=5\n";

# the important settings
print PGCONF "max_connections=15\n";
print PGCONF "superuser_reserved_connections=3\n";
print PGCONF "max_wal_senders=2\n";

close(PGCONF);

# write the pg_hba.conf
open(PGHBA, ">>${ROOTDIR}/main1/pg_hba.conf");
print PGHBA "local replication  repl  trust\n";
close(PGHBA);

# start the server
system("$PG_CTL -D ${ROOTDIR}/main1 -o '-p 10001' -l ${ROOTDIR}/main1.log start");

# create the replication user
print("* creating user accounts\n");
my $dbmain = DBI->connect('dbi:Pg:dbname=postgres;port=10001', '', '')
  or die "Error: $DBI::errstr\n";
$dbmain->do('create role repl replication login;');
$dbmain->do('create role test login;');
$dbmain->disconnect;

# create three slaves for playing around
print("* Creating 3 slaves\n");
system("${PG_BASEBACKUP} -D ${ROOTDIR}/slave1 -R -U repl -p 10001");
system("${PG_BASEBACKUP} -D ${ROOTDIR}/slave2 -R -U repl -p 10001");
system("${PG_BASEBACKUP} -D ${ROOTDIR}/slave3 -R -U repl -p 10001");

# start 10 connections to the master, so that it is full
print("* creating user connections\n");
my @connections = ();
foreach my $i (0..11) {
  my $conn = DBI->connect('dbi:Pg:dbname=postgres;port=10001', 'test', '')
      or die "Normal connection did not work! $DBI::errstr\n";
  push(@connections, $conn);
}
printf("connections: %i\n", scalar(@connections));

# now we start the slaves to see, if the patch works
print("* starting slaves for test\n");
system("${PG_CTL} -D ${ROOTDIR}/slave1 -t 5 -o '-p 10002' -l ${ROOTDIR}/slave1.log start");
system("${PG_CTL} -D ${ROOTDIR}/slave2 -t 5 -o '-p 10003' -l ${ROOTDIR}/slave2.log start");

# check for error messages in the last slave
my $log = File::ReadBackwards->new("${ROOTDIR}/slave2.log");
my $i = 0;
while (! $log->eof && $i < 10) {
  if ($log->readline =~ /remaining connection slots are reserved/) {
    $success = 'failed';
  }
}
$log->close;

# test what happens with the third one
print("testing that third node can't access\n");
system("${PG_CTL} -D ${ROOTDIR}/slave3 -t 5 -o '-p 10004' -l ${ROOTDIR}/slave3.log start");
open(SLAVE3LOG, "${ROOTDIR}/slave3.log");
my $found = 0;
while(! eof(SLAVE3LOG)) {
  my $line = <SLAVE3LOG>;
  if ($line =~ /exceeds max_wal_senders/) {
    $found = 1;
  }
}
close(SLAVE3LOG);
if ($found == 0) {
  $success = 'failed';
}

# stop the slaves after the test
print("* stopping slaves after test\n");
system("${PG_CTL} -D ${ROOTDIR}/slave1 stop");
system("${PG_CTL} -D ${ROOTDIR}/slave2 stop");
system("${PG_CTL} -D ${ROOTDIR}/slave3 stop");

print("* beginning cleanup\n");
system("$PG_CTL -D ${ROOTDIR}/main1 -m fast stop");
remove_tree("${ROOTDIR}/main1", {verbose => 0});
remove_tree("${ROOTDIR}/slave1", {verbose => 0});
remove_tree("${ROOTDIR}/slave2", {verbose => 0});
remove_tree("${ROOTDIR}/slave3", {verbose => 0});

print("\ntests: $success\n");
