From 93a79d85670c0a915da6daef4c53629587626efd Mon Sep 17 00:00:00 2001
From: Amul Sul <amul.sul@enterprisedb.com>
Date: Fri, 27 Aug 2021 08:18:40 -0400
Subject: [PATCH v43 6/6] Test: Few tap tests for wal prohibited system

Does following testing:

1. Basic verification like insert into normal and unlogged table on
   wal prohibited system.
2. Check permission to non-superuser to alter wal prohibited system
   state.
3. Verify open write transaction disconnection when system state has
   been changed to wal prohibited.
4. Verify wal write and checkpoint lsn after restart of wal prohibited
   system doesn't change along with wal prohibited state.
5. At restart wal prohibited system shutdown and on start recovery end
   checkpoint is skipped, verify implicit checkpoint perform when
   system state changes to wal permitted.
6. Standby server cannot be in wal prohibited, standby.signal and/or
   recovery.signal take out system from wal prohibited state.
7. Terminate session running transaction performed write but not
   committed yet while changing state to WAL prohibited.
8. Changes 026_overwrite_contrecord.pl test to check with WAL
   prohibited system. (XXX: Should make copy of this file for WAL
   prohibited testing, I think, not needed).
---
 .../recovery/t/026_overwrite_contrecord.pl    |  11 +-
 src/test/recovery/t/027_pg_prohibit_wal.pl    | 216 ++++++++++++++++++
 2 files changed, 223 insertions(+), 4 deletions(-)
 create mode 100644 src/test/recovery/t/027_pg_prohibit_wal.pl

diff --git a/src/test/recovery/t/026_overwrite_contrecord.pl b/src/test/recovery/t/026_overwrite_contrecord.pl
index b78c2fd7912..2dfb1d22809 100644
--- a/src/test/recovery/t/026_overwrite_contrecord.pl
+++ b/src/test/recovery/t/026_overwrite_contrecord.pl
@@ -65,10 +65,11 @@ my $endfile = $node->safe_psql('postgres',
 	'SELECT pg_walfile_name(pg_current_wal_insert_lsn())');
 ok($initfile ne $endfile, "$initfile differs from $endfile");
 
-# Now stop abruptly, to avoid a stop checkpoint.  We can remove the tail file
-# afterwards, and on startup the large message should be overwritten with new
-# contents
-$node->stop('immediate');
+# Change system to wal prohibited that will skip shutdown checkpoint.  We can
+# remove the tail file afterwards, and on startup the large message should be
+# overwritten with new contents
+$node->safe_psql('postgres', qq{SELECT pg_prohibit_wal(true)});
+$node->stop;
 
 unlink $node->basedir . "/pgdata/pg_wal/$endfile"
   or die "could not unlink " . $node->basedir . "/pgdata/pg_wal/$endfile: $!";
@@ -81,6 +82,8 @@ $node_standby->init_from_backup($node, 'backup', has_streaming => 1);
 $node_standby->start;
 $node->start;
 
+# Change system to wal permitted now.
+$node->safe_psql('postgres', qq{SELECT pg_prohibit_wal(false)});
 $node->safe_psql('postgres',
 	qq{create table foo (a text); insert into foo values ('hello')});
 $node->safe_psql('postgres',
diff --git a/src/test/recovery/t/027_pg_prohibit_wal.pl b/src/test/recovery/t/027_pg_prohibit_wal.pl
new file mode 100644
index 00000000000..b426cfc9aa0
--- /dev/null
+++ b/src/test/recovery/t/027_pg_prohibit_wal.pl
@@ -0,0 +1,216 @@
+
+# Copyright (c) 2021, PostgreSQL Global Development Group
+
+# Test wal prohibited state.
+use strict;
+use warnings;
+use FindBin;
+use PostgreSQL::Test::Cluster;
+use PostgreSQL::Test::Utils;
+use Test::More;
+use Test::More tests => 22;
+
+# Query to read wal_prohibited GUC
+my $show_wal_prohibited_query = "SELECT current_setting('wal_prohibited')";
+
+# Initialize database node
+my $node_primary = PostgreSQL::Test::Cluster->new('primary');
+$node_primary->init(has_archiving => 1, allows_streaming => 1);
+$node_primary->start;
+
+# Create few tables and insert some data
+$node_primary->safe_psql('postgres',  <<EOSQL);
+CREATE TABLE tab AS SELECT 1 AS i;
+CREATE UNLOGGED TABLE unlogtab AS SELECT 1 AS i;
+EOSQL
+
+# Change to WAL prohibited
+$node_primary->safe_psql('postgres', 'SELECT pg_prohibit_wal(true)');
+is($node_primary->safe_psql('postgres', $show_wal_prohibited_query), 'on',
+	'server is now wal prohibited');
+
+#
+# In wal prohibited state, further table insert will fail.
+#
+# Note that even though inter into unlogged and temporary table doesn't generate
+# wal but the transaction does that insert operation will acquire transaction id
+# which is not allowed on wal prohibited system. Also, that transaction's abort
+# or commit state will be wal logged at the end which is prohibited as well.
+#
+my ($stdout, $stderr, $timed_out);
+$node_primary->psql('postgres', 'INSERT INTO tab VALUES(2)',
+          stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/cannot execute INSERT in a read-only transaction/,
+	'server is wal prohibited, table insert is failed');
+$node_primary->psql('postgres', 'INSERT INTO unlogtab VALUES(2)',
+          stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/cannot execute INSERT in a read-only transaction/,
+	'server is wal prohibited, unlogged table insert is failed');
+
+# Get current wal write and latest checkpoint lsn
+my $write_lsn = $node_primary->lsn('write');
+my $checkpoint_lsn = get_latest_checkpoint_location($node_primary);
+
+# Restart the server, shutdown and starup checkpoint will be skipped.
+$node_primary->restart;
+
+is($node_primary->safe_psql('postgres', $show_wal_prohibited_query), 'on',
+	'server is wal prohibited after restart too');
+is($node_primary->lsn('write'), $write_lsn,
+	"no wal writes on server, last wal write lsn : $write_lsn");
+is(get_latest_checkpoint_location($node_primary), $checkpoint_lsn,
+	"no new checkpoint, last checkpoint lsn : $checkpoint_lsn");
+
+# Change server to WAL permitted
+$node_primary->safe_psql('postgres', 'SELECT pg_prohibit_wal(false)');
+is($node_primary->safe_psql('postgres', $show_wal_prohibited_query),
+	'off', 'server is change to wal permitted');
+
+my $new_checkpoint_lsn = get_latest_checkpoint_location($node_primary);
+is($new_checkpoint_lsn ne $checkpoint_lsn, 1,
+	"new checkpoint performed, new checkpoint lsn : $new_checkpoint_lsn");
+
+my $new_write_lsn = $node_primary->lsn('write');
+is($new_write_lsn ne $write_lsn, 1,
+	"new wal writes on server, new latest wal write lsn : $new_write_lsn");
+
+# Insert data
+$node_primary->safe_psql('postgres', 'INSERT INTO tab VALUES(2)');
+is($node_primary->safe_psql('postgres', 'SELECT count(i) FROM tab'), '2',
+	'table insert passed');
+
+# Only the superuser and the user who granted permission able to call
+# pg_prohibit_wal to change wal prohibited state.
+$node_primary->safe_psql('postgres', 'CREATE USER non_superuser');
+$node_primary->psql('postgres', 'SELECT pg_prohibit_wal(true)',
+	stdout => \$stdout, stderr => \$stderr, extra_params => [ '-U', 'non_superuser' ]);
+like($stderr, qr/permission denied for function pg_prohibit_wal/,
+	'permission denied to non-superuser for alter wal prohibited state');
+$node_primary->safe_psql('postgres', 'GRANT EXECUTE ON FUNCTION pg_prohibit_wal TO non_superuser');
+$node_primary->psql('postgres', 'SELECT pg_prohibit_wal(true)',
+	stdout => \$stdout, stderr => \$stderr, extra_params => [ '-U', 'non_superuser' ]);
+is($node_primary->safe_psql('postgres', $show_wal_prohibited_query), 'on',
+	'granted permission to non-superuser, able to alter wal prohibited state');
+
+# back to normal state
+$node_primary->psql('postgres', 'SELECT pg_prohibit_wal(false)');
+
+my $psql_timeout = IPC::Run::timer(60);
+my ($mysession_stdin, $mysession_stdout, $mysession_stderr) = ('', '', '');
+my $mysession = IPC::Run::start(
+	[
+		'psql', '-X', '-qAt', '-v', 'ON_ERROR_STOP=1', '-f', '-', '-d',
+		$node_primary->connstr('postgres')
+	],
+	'<',
+	\$mysession_stdin,
+	'>',
+	\$mysession_stdout,
+	'2>',
+	\$mysession_stderr,
+	$psql_timeout);
+
+# Write in transaction and get backend pid
+$mysession_stdin .= q[
+BEGIN;
+INSERT INTO tab VALUES(4);
+SELECT $$value-4-inserted-into-tab$$;
+];
+$mysession->pump until $mysession_stdout =~ /value-4-inserted-into-tab[\r\n]$/;
+like($mysession_stdout, qr/value-4-inserted-into-tab/,
+	'started write transaction in a session');
+$mysession_stdout = '';
+$mysession_stderr = '';
+
+# Change to WAL prohibited
+$node_primary->safe_psql('postgres', 'SELECT pg_prohibit_wal(true)');
+is($node_primary->safe_psql('postgres', $show_wal_prohibited_query), 'on',
+	'server is changed to wal prohibited by another session');
+
+# Try to commit open write transaction.
+$mysession_stdin .= q[
+COMMIT;
+];
+$mysession->pump;
+like($mysession_stderr, qr/FATAL:  WAL is now prohibited/,
+	'session with open write transaction is terminated');
+
+# Now stop the primary server in WAL prohibited state and take filesystem level
+# backup and set up new server from it.
+$node_primary->stop;
+my $backup_name = 'my_backup';
+$node_primary->backup_fs_cold($backup_name);
+my $node_standby = PostgreSQL::Test::Cluster->new('standby');
+$node_standby->init_from_backup($node_primary, $backup_name);
+$node_standby->start;
+
+# The primary server is stopped in wal prohibited state, the filesystem level
+# copy also be in wal prohibited state
+is($node_standby->safe_psql('postgres', $show_wal_prohibited_query), 'on',
+	'new server created using backup of a stopped primary is also wal prohibited');
+
+# Start Primary
+$node_primary->start;
+
+# Set the new server as standby of primary.
+# enable_streaming will create standby.signal file which will take out system
+# from wal prohibited state.
+$node_standby->enable_streaming($node_primary);
+$node_standby->restart;
+
+# Check if the new server has been taken out from the wal prohibited state.
+is($node_standby->safe_psql('postgres', $show_wal_prohibited_query),
+	'off', 'new server as standby is no longer wal prohibited');
+
+# Recovery server cannot be put into wal prohibited state.
+$node_standby->psql('postgres', 'SELECT pg_prohibit_wal(true)',
+          stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/cannot execute pg_prohibit_wal\(\) during recovery/,
+	'standby server state cannot be changed to wal prohibited');
+
+# Primary is still in wal prohibited state, the further insert will fail.
+$node_primary->psql('postgres', 'INSERT INTO tab VALUES(3)',
+          stdout => \$stdout, stderr => \$stderr);
+like($stderr, qr/cannot execute INSERT in a read-only transaction/,
+	'primary server is wal prohibited, table insert is failed');
+
+# Change primary to WAL permitted
+$node_primary->safe_psql('postgres', 'SELECT pg_prohibit_wal(false)');
+is($node_primary->safe_psql('postgres', $show_wal_prohibited_query),
+	'off', 'primary server is change to wal permitted');
+
+# Insert data
+$node_primary->safe_psql('postgres', 'INSERT INTO tab VALUES(3)');
+is($node_primary->safe_psql('postgres', 'SELECT count(i) FROM tab'), '3',
+	'insert passed on primary');
+
+# Wait for standbys to catch up
+$node_primary->wait_for_catchup($node_standby, 'write');
+is($node_standby->safe_psql('postgres', 'SELECT count(i) FROM tab'), '3',
+	'new insert replicated on standby as well');
+
+
+#
+# Get latest checkpoint lsn from control file
+#
+sub get_latest_checkpoint_location
+{
+	my ($node) = @_;
+	my $data_dir = $node->data_dir;
+	my ($stdout, $stderr) = run_command([ 'pg_controldata', $data_dir ]);
+	my @control_data = split("\n", $stdout);
+
+	my $latest_checkpoint_lsn = undef;
+	foreach (@control_data)
+	{
+		if ($_ =~ /^Latest checkpoint location:\s*(.*)$/mg)
+		{
+			$latest_checkpoint_lsn = $1;
+			last;
+		}
+	}
+	die "No latest checkpoint location in control file found\n"
+	unless defined($latest_checkpoint_lsn);
+
+	return $latest_checkpoint_lsn;
+}
-- 
2.18.0

