|
| 1 | + |
| 2 | +# Copyright (c) 2021-2024, PostgreSQL Global Development Group |
| 3 | + |
| 4 | +use strict; |
| 5 | +use warnings FATAL => 'all'; |
| 6 | +use PostgreSQL::Test::Utils; |
| 7 | +use Test::More; |
| 8 | + |
| 9 | +use FindBin; |
| 10 | +use lib $FindBin::RealBin; |
| 11 | + |
| 12 | +use RewindTest; |
| 13 | + |
| 14 | +sub run_test |
| 15 | +{ |
| 16 | + my $test_mode = shift; |
| 17 | + |
| 18 | + RewindTest::setup_cluster($test_mode); |
| 19 | + RewindTest::start_primary(); |
| 20 | + |
| 21 | + # Create an in-place tablespace with some data on it. |
| 22 | + primary_psql("CREATE TABLESPACE space_test LOCATION ''"); |
| 23 | + primary_psql("CREATE TABLE space_tbl (d text) TABLESPACE space_test"); |
| 24 | + primary_psql( |
| 25 | + "INSERT INTO space_tbl VALUES ('in primary, before promotion')"); |
| 26 | + |
| 27 | + # Create a test table and insert a row in primary. |
| 28 | + primary_psql("CREATE TABLE tbl1 (d text)"); |
| 29 | + primary_psql("INSERT INTO tbl1 VALUES ('in primary')"); |
| 30 | + |
| 31 | + # This test table will be used to test truncation, i.e. the table |
| 32 | + # is extended in the old primary after promotion |
| 33 | + primary_psql("CREATE TABLE trunc_tbl (d text)"); |
| 34 | + primary_psql("INSERT INTO trunc_tbl VALUES ('in primary')"); |
| 35 | + |
| 36 | + # This test table will be used to test the "copy-tail" case, i.e. the |
| 37 | + # table is truncated in the old primary after promotion |
| 38 | + primary_psql("CREATE TABLE tail_tbl (id integer, d text)"); |
| 39 | + primary_psql("INSERT INTO tail_tbl VALUES (0, 'in primary')"); |
| 40 | + |
| 41 | + # This test table is dropped in the old primary after promotion. |
| 42 | + primary_psql("CREATE TABLE drop_tbl (d text)"); |
| 43 | + primary_psql("INSERT INTO drop_tbl VALUES ('in primary')"); |
| 44 | + |
| 45 | + primary_psql("CHECKPOINT"); |
| 46 | + |
| 47 | + RewindTest::create_standby($test_mode); |
| 48 | + |
| 49 | + # Insert additional data on primary that will be replicated to standby |
| 50 | + primary_psql("INSERT INTO tbl1 values ('in primary, before promotion')"); |
| 51 | + primary_psql( |
| 52 | + "INSERT INTO trunc_tbl values ('in primary, before promotion')"); |
| 53 | + primary_psql( |
| 54 | + "INSERT INTO tail_tbl SELECT g, 'in primary, before promotion: ' || g FROM generate_series(1, 10000) g" |
| 55 | + ); |
| 56 | + |
| 57 | + primary_psql('CHECKPOINT'); |
| 58 | + |
| 59 | + RewindTest::promote_standby(); |
| 60 | + |
| 61 | + # Insert a row in the old primary. This causes the primary and standby |
| 62 | + # to have "diverged", it's no longer possible to just apply the |
| 63 | + # standby's logs over primary directory - you need to rewind. |
| 64 | + primary_psql("INSERT INTO tbl1 VALUES ('in primary, after promotion')"); |
| 65 | + |
| 66 | + # Also insert a new row in the standby, which won't be present in the |
| 67 | + # old primary. |
| 68 | + standby_psql("INSERT INTO tbl1 VALUES ('in standby, after promotion')"); |
| 69 | + |
| 70 | + # Insert enough rows to trunc_tbl to extend the file. pg_rewind should |
| 71 | + # truncate it back to the old size. |
| 72 | + primary_psql( |
| 73 | + "INSERT INTO trunc_tbl SELECT 'in primary, after promotion: ' || g FROM generate_series(1, 10000) g" |
| 74 | + ); |
| 75 | + |
| 76 | + # Truncate tail_tbl. pg_rewind should copy back the truncated part |
| 77 | + # (We cannot use an actual TRUNCATE command here, as that creates a |
| 78 | + # whole new relfilenode) |
| 79 | + primary_psql("DELETE FROM tail_tbl WHERE id > 10"); |
| 80 | + primary_psql("VACUUM tail_tbl"); |
| 81 | + |
| 82 | + # Drop drop_tbl. pg_rewind should copy it back. |
| 83 | + primary_psql( |
| 84 | + "insert into drop_tbl values ('in primary, after promotion')"); |
| 85 | + primary_psql("DROP TABLE drop_tbl"); |
| 86 | + |
| 87 | + # Insert some data in the in-place tablespace for the old primary and |
| 88 | + # the standby. |
| 89 | + primary_psql( |
| 90 | + "INSERT INTO space_tbl VALUES ('in primary, after promotion')"); |
| 91 | + standby_psql( |
| 92 | + "INSERT INTO space_tbl VALUES ('in standby, after promotion')"); |
| 93 | + |
| 94 | + # Before running pg_rewind, do a couple of extra tests with several |
| 95 | + # option combinations. As the code paths taken by those tests |
| 96 | + # do not change for the "local" and "remote" modes, just run them |
| 97 | + # in "local" mode for simplicity's sake. |
| 98 | + if ($test_mode eq 'local') |
| 99 | + { |
| 100 | + my $primary_pgdata = $node_primary->data_dir; |
| 101 | + my $standby_pgdata = $node_standby->data_dir; |
| 102 | + |
| 103 | + # First check that pg_rewind fails if the target cluster is |
| 104 | + # not stopped as it fails to start up for the forced recovery |
| 105 | + # step. |
| 106 | + command_fails( |
| 107 | + [ |
| 108 | + 'pg_rewind', '--debug', |
| 109 | + '--source-pgdata', $standby_pgdata, |
| 110 | + '--target-pgdata', $primary_pgdata, |
| 111 | + '--no-sync' |
| 112 | + ], |
| 113 | + 'pg_rewind with running target'); |
| 114 | + |
| 115 | + # Again with --no-ensure-shutdown, which should equally fail. |
| 116 | + # This time pg_rewind complains without attempting to perform |
| 117 | + # recovery once. |
| 118 | + command_fails( |
| 119 | + [ |
| 120 | + 'pg_rewind', '--debug', |
| 121 | + '--source-pgdata', $standby_pgdata, |
| 122 | + '--target-pgdata', $primary_pgdata, |
| 123 | + '--no-sync', '--no-ensure-shutdown' |
| 124 | + ], |
| 125 | + 'pg_rewind --no-ensure-shutdown with running target'); |
| 126 | + |
| 127 | + # Stop the target, and attempt to run with a local source |
| 128 | + # still running. This fails as pg_rewind requires to have |
| 129 | + # a source cleanly stopped. |
| 130 | + $node_primary->stop; |
| 131 | + command_fails( |
| 132 | + [ |
| 133 | + 'pg_rewind', '--debug', |
| 134 | + '--source-pgdata', $standby_pgdata, |
| 135 | + '--target-pgdata', $primary_pgdata, |
| 136 | + '--no-sync', '--no-ensure-shutdown' |
| 137 | + ], |
| 138 | + 'pg_rewind with unexpected running source'); |
| 139 | + |
| 140 | + # Stop the target cluster cleanly, and run again pg_rewind |
| 141 | + # with --dry-run mode. If anything gets generated in the data |
| 142 | + # folder, the follow-up run of pg_rewind will most likely fail, |
| 143 | + # so keep this test as the last one of this subset. |
| 144 | + $node_standby->stop; |
| 145 | + command_ok( |
| 146 | + [ |
| 147 | + 'pg_rewind', '--debug', |
| 148 | + '--source-pgdata', $standby_pgdata, |
| 149 | + '--target-pgdata', $primary_pgdata, |
| 150 | + '--no-sync', '--dry-run' |
| 151 | + ], |
| 152 | + 'pg_rewind --dry-run'); |
| 153 | + |
| 154 | + # Both clusters need to be alive moving forward. |
| 155 | + $node_standby->start; |
| 156 | + $node_primary->start; |
| 157 | + } |
| 158 | + |
| 159 | + RewindTest::run_pg_rewind($test_mode); |
| 160 | + |
| 161 | + check_query( |
| 162 | + 'SELECT * FROM space_tbl ORDER BY d', |
| 163 | + qq(in primary, before promotion |
| 164 | +in standby, after promotion |
| 165 | +), |
| 166 | + 'table content'); |
| 167 | + |
| 168 | + check_query( |
| 169 | + 'SELECT * FROM tbl1', |
| 170 | + qq(in primary |
| 171 | +in primary, before promotion |
| 172 | +in standby, after promotion |
| 173 | +), |
| 174 | + 'table content'); |
| 175 | + |
| 176 | + check_query( |
| 177 | + 'SELECT * FROM trunc_tbl', |
| 178 | + qq(in primary |
| 179 | +in primary, before promotion |
| 180 | +), |
| 181 | + 'truncation'); |
| 182 | + |
| 183 | + check_query( |
| 184 | + 'SELECT count(*) FROM tail_tbl', |
| 185 | + qq(10001 |
| 186 | +), |
| 187 | + 'tail-copy'); |
| 188 | + |
| 189 | + check_query( |
| 190 | + 'SELECT * FROM drop_tbl', |
| 191 | + qq(in primary |
| 192 | +), |
| 193 | + 'drop'); |
| 194 | + |
| 195 | + # Permissions on PGDATA should be default |
| 196 | + SKIP: |
| 197 | + { |
| 198 | + skip "unix-style permissions not supported on Windows", 1 |
| 199 | + if ($windows_os); |
| 200 | + |
| 201 | + ok(check_mode_recursive($node_primary->data_dir(), 0700, 0600), |
| 202 | + 'check PGDATA permissions'); |
| 203 | + } |
| 204 | + |
| 205 | + RewindTest::clean_rewind_test(); |
| 206 | + return; |
| 207 | +} |
| 208 | + |
| 209 | +# Run the test in both modes |
| 210 | +run_test('local'); |
| 211 | +run_test('remote'); |
| 212 | +run_test('archive'); |
| 213 | + |
| 214 | +done_testing(); |
0 commit comments