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