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