1package RewindTest;
2
3# Test driver for pg_rewind. Each test consists of a cycle where a new cluster
4# is first created with initdb, and a streaming replication standby is set up
5# to follow the master. Then the master is shut down and the standby is
6# promoted, and finally pg_rewind is used to rewind the old master, using the
7# standby as the source.
8#
9# To run a test, the test script (in t/ subdirectory) calls the functions
10# in this module. These functions should be called in this sequence:
11#
12# 1. setup_cluster - creates a PostgreSQL cluster that runs as the master
13#
14# 2. start_master - starts the master server
15#
16# 3. create_standby - runs pg_basebackup to initialize a standby server, and
17#    sets it up to follow the master.
18#
19# 4. promote_standby - runs "pg_ctl promote" to promote the standby server.
20# The old master keeps running.
21#
22# 5. run_pg_rewind - stops the old master (if it's still running) and runs
23# pg_rewind to synchronize it with the now-promoted standby server.
24#
25# 6. clean_rewind_test - stops both servers used in the test, if they're
26# still running.
27#
28# The test script can use the helper functions master_psql and standby_psql
29# to run psql against the master and standby servers, respectively. The
30# test script can also use the $connstr_master and $connstr_standby global
31# variables, which contain libpq connection strings for connecting to the
32# master and standby servers. The data directories are also available
33# in paths $test_master_datadir and $test_standby_datadir
34
35use strict;
36use warnings;
37
38use Carp;
39use Config;
40use Exporter 'import';
41use File::Copy;
42use File::Path qw(rmtree);
43use IPC::Run qw(run);
44use PostgresNode;
45use TestLib;
46use Test::More;
47
48our @EXPORT = qw(
49  $node_master
50  $node_standby
51
52  master_psql
53  standby_psql
54  check_query
55
56  setup_cluster
57  start_master
58  create_standby
59  promote_standby
60  run_pg_rewind
61  clean_rewind_test
62);
63
64# Our nodes.
65our $node_master;
66our $node_standby;
67
68sub master_psql
69{
70	my $cmd = shift;
71	my $dbname = shift || 'postgres';
72
73	system_or_bail 'psql', '-q', '--no-psqlrc', '-d',
74	  $node_master->connstr($dbname), '-c', "$cmd";
75	return;
76}
77
78sub standby_psql
79{
80	my $cmd = shift;
81	my $dbname = shift || 'postgres';
82
83	system_or_bail 'psql', '-q', '--no-psqlrc', '-d',
84	  $node_standby->connstr($dbname), '-c', "$cmd";
85	return;
86}
87
88# Run a query against the master, and check that the output matches what's
89# expected
90sub check_query
91{
92	my ($query, $expected_stdout, $test_name) = @_;
93	my ($stdout, $stderr);
94
95	# we want just the output, no formatting
96	my $result = run [
97		'psql', '-q', '-A', '-t', '--no-psqlrc', '-d',
98		$node_master->connstr('postgres'),
99		'-c', $query
100	  ],
101	  '>', \$stdout, '2>', \$stderr;
102
103	# We don't use ok() for the exit code and stderr, because we want this
104	# check to be just a single test.
105	if (!$result)
106	{
107		fail("$test_name: psql exit code");
108	}
109	elsif ($stderr ne '')
110	{
111		diag $stderr;
112		fail("$test_name: psql no stderr");
113	}
114	else
115	{
116		$stdout =~ s/\r\n/\n/g if $Config{osname} eq 'msys';
117		is($stdout, $expected_stdout, "$test_name: query result matches");
118	}
119	return;
120}
121
122sub setup_cluster
123{
124	my $extra_name = shift;    # Used to differentiate clusters
125	my $extra      = shift;    # Extra params for initdb
126
127	# Initialize master, data checksums are mandatory
128	$node_master =
129	  get_new_node('master' . ($extra_name ? "_${extra_name}" : ''));
130	$node_master->init(allows_streaming => 1, extra => $extra);
131
132	# Set wal_keep_segments to prevent WAL segment recycling after enforced
133	# checkpoints in the tests.
134	$node_master->append_conf(
135		'postgresql.conf', qq(
136wal_keep_segments = 20
137));
138	return;
139}
140
141sub start_master
142{
143	$node_master->start;
144
145	#### Now run the test-specific parts to initialize the master before setting
146	# up standby
147
148	return;
149}
150
151sub create_standby
152{
153	my $extra_name = shift;
154
155	$node_standby =
156	  get_new_node('standby' . ($extra_name ? "_${extra_name}" : ''));
157	$node_master->backup('my_backup');
158	$node_standby->init_from_backup($node_master, 'my_backup');
159	my $connstr_master = $node_master->connstr();
160
161	$node_standby->append_conf(
162		"recovery.conf", qq(
163primary_conninfo='$connstr_master application_name=rewind_standby'
164standby_mode=on
165recovery_target_timeline='latest'
166));
167
168	# Start standby
169	$node_standby->start;
170
171	# The standby may have WAL to apply before it matches the primary.  That
172	# is fine, because no test examines the standby before promotion.
173
174	return;
175}
176
177sub promote_standby
178{
179	#### Now run the test-specific parts to run after standby has been started
180	# up standby
181
182	# Wait for the standby to receive and write all WAL.
183	$node_master->wait_for_catchup('rewind_standby', 'write');
184
185	# Now promote standby and insert some new data on master, this will put
186	# the master out-of-sync with the standby.
187	$node_standby->promote;
188
189	# Force a checkpoint after the promotion. pg_rewind looks at the control
190	# file to determine what timeline the server is on, and that isn't updated
191	# immediately at promotion, but only at the next checkpoint. When running
192	# pg_rewind in remote mode, it's possible that we complete the test steps
193	# after promotion so quickly that when pg_rewind runs, the standby has not
194	# performed a checkpoint after promotion yet.
195	standby_psql("checkpoint");
196
197	return;
198}
199
200sub run_pg_rewind
201{
202	my $test_mode       = shift;
203	my $master_pgdata   = $node_master->data_dir;
204	my $standby_pgdata  = $node_standby->data_dir;
205	my $standby_connstr = $node_standby->connstr('postgres');
206	my $tmp_folder      = TestLib::tempdir;
207
208	# Stop the master and be ready to perform the rewind
209	$node_master->stop;
210
211	# At this point, the rewind processing is ready to run.
212	# We now have a very simple scenario with a few diverged WAL record.
213	# The real testing begins really now with a bifurcation of the possible
214	# scenarios that pg_rewind supports.
215
216	# Keep a temporary postgresql.conf for master node or it would be
217	# overwritten during the rewind.
218	copy(
219		"$master_pgdata/postgresql.conf",
220		"$tmp_folder/master-postgresql.conf.tmp");
221
222	# Now run pg_rewind
223	if ($test_mode eq "local")
224	{
225
226		# Do rewind using a local pgdata as source
227		# Stop the master and be ready to perform the rewind
228		$node_standby->stop;
229		command_ok(
230			[
231				'pg_rewind',
232				"--debug",
233				"--source-pgdata=$standby_pgdata",
234				"--target-pgdata=$master_pgdata"
235			],
236			'pg_rewind local');
237	}
238	elsif ($test_mode eq "remote")
239	{
240
241		# Do rewind using a remote connection as source
242		command_ok(
243			[
244				'pg_rewind',       "--debug",
245				"--source-server", $standby_connstr,
246				"--target-pgdata=$master_pgdata"
247			],
248			'pg_rewind remote');
249	}
250	else
251	{
252
253		# Cannot come here normally
254		croak("Incorrect test mode specified");
255	}
256
257	# Now move back postgresql.conf with old settings
258	move(
259		"$tmp_folder/master-postgresql.conf.tmp",
260		"$master_pgdata/postgresql.conf");
261
262	chmod(
263		$node_master->group_access() ? 0640 : 0600,
264		"$master_pgdata/postgresql.conf")
265	  or BAIL_OUT(
266		"unable to set permissions for $master_pgdata/postgresql.conf");
267
268	# Plug-in rewound node to the now-promoted standby node
269	my $port_standby = $node_standby->port;
270	$node_master->append_conf(
271		'recovery.conf', qq(
272primary_conninfo='port=$port_standby'
273standby_mode=on
274recovery_target_timeline='latest'
275));
276
277	# Restart the master to check that rewind went correctly
278	$node_master->start;
279
280	#### Now run the test-specific parts to check the result
281
282	return;
283}
284
285# Clean up after the test. Stop both servers, if they're still running.
286sub clean_rewind_test
287{
288	$node_master->teardown_node  if defined $node_master;
289	$node_standby->teardown_node if defined $node_standby;
290	return;
291}
292
2931;
294