|
| 1 | + |
| 2 | +# Copyright (c) 2024, PostgreSQL Global Development Group |
| 3 | + |
| 4 | +# Test low-level backup method by using pg_backup_start() and pg_backup_stop() |
| 5 | +# to create backups. |
| 6 | + |
| 7 | +use strict; |
| 8 | +use warnings; |
| 9 | + |
| 10 | +use File::Copy qw(copy); |
| 11 | +use File::Path qw(rmtree); |
| 12 | +use PostgreSQL::Test::Cluster; |
| 13 | +use PostgreSQL::Test::Utils; |
| 14 | +use Test::More; |
| 15 | + |
| 16 | +# Start primary node with archiving. |
| 17 | +my $node_primary = PostgreSQL::Test::Cluster->new('primary'); |
| 18 | +$node_primary->init(has_archiving => 1, allows_streaming => 1); |
| 19 | +$node_primary->start; |
| 20 | + |
| 21 | +# Start backup. |
| 22 | +my $backup_name = 'backup1'; |
| 23 | +my $psql = $node_primary->background_psql('postgres'); |
| 24 | + |
| 25 | +$psql->query_safe("SET client_min_messages TO WARNING"); |
| 26 | +$psql->set_query_timer_restart; |
| 27 | +$psql->query_safe("select pg_backup_start('test label')"); |
| 28 | + |
| 29 | +# Copy files. |
| 30 | +my $backup_dir = $node_primary->backup_dir . '/' . $backup_name; |
| 31 | + |
| 32 | +PostgreSQL::Test::RecursiveCopy::copypath($node_primary->data_dir, |
| 33 | + $backup_dir); |
| 34 | + |
| 35 | +# Cleanup some files/paths that should not be in the backup. There is no |
| 36 | +# attempt to handle all the exclusions done by pg_basebackup here, in part |
| 37 | +# because these are not required, but also to keep the test simple. |
| 38 | +# |
| 39 | +# Also remove pg_control because it needs to be copied later. |
| 40 | +unlink("$backup_dir/postmaster.pid") |
| 41 | + or BAIL_OUT("unable to unlink $backup_dir/postmaster.pid"); |
| 42 | +unlink("$backup_dir/postmaster.opts") |
| 43 | + or BAIL_OUT("unable to unlink $backup_dir/postmaster.opts"); |
| 44 | +unlink("$backup_dir/global/pg_control") |
| 45 | + or BAIL_OUT("unable to unlink $backup_dir/global/pg_control"); |
| 46 | + |
| 47 | +rmtree("$backup_dir/pg_wal") |
| 48 | + or BAIL_OUT("unable to unlink contents of $backup_dir/pg_wal"); |
| 49 | +mkdir("$backup_dir/pg_wal"); |
| 50 | + |
| 51 | +# Create a table that will be used to verify that recovery started at the |
| 52 | +# correct location, rather than a location recorded in the control file. |
| 53 | +$node_primary->safe_psql('postgres', "create table canary (id int)"); |
| 54 | + |
| 55 | +# Advance the checkpoint location in pg_control past the location where the |
| 56 | +# backup started. Switch WAL to make it really clear that the location is |
| 57 | +# different and to put the checkpoint in a new WAL segment. |
| 58 | +my $segment_name = $node_primary->safe_psql('postgres', |
| 59 | + "select pg_walfile_name(pg_switch_wal())"); |
| 60 | + |
| 61 | +# Ensure that the segment just switched from is archived. The follow-up |
| 62 | +# tests depend on its presence to begin recovery. |
| 63 | +$node_primary->poll_query_until('postgres', |
| 64 | + q{SELECT last_archived_wal FROM pg_stat_archiver}, |
| 65 | + $segment_name) |
| 66 | + or die |
| 67 | + "Timed out while waiting for archiving of switched segment to finish"; |
| 68 | + |
| 69 | +$node_primary->safe_psql('postgres', "checkpoint"); |
| 70 | + |
| 71 | +# Copy pg_control last so it contains the new checkpoint. |
| 72 | +copy($node_primary->data_dir . '/global/pg_control', |
| 73 | + "$backup_dir/global/pg_control") |
| 74 | + or BAIL_OUT("unable to copy global/pg_control"); |
| 75 | + |
| 76 | +# Save the name segment that will be archived by pg_backup_stop(). |
| 77 | +# This is copied to the pg_wal directory of the node whose recovery |
| 78 | +# is done without a backup_label. |
| 79 | +my $stop_segment_name = $node_primary->safe_psql('postgres', |
| 80 | + 'SELECT pg_walfile_name(pg_current_wal_lsn())'); |
| 81 | + |
| 82 | +# Stop backup and get backup_label, the last segment is archived. |
| 83 | +my $backup_label = |
| 84 | + $psql->query_safe("select labelfile from pg_backup_stop()"); |
| 85 | + |
| 86 | +$psql->quit; |
| 87 | + |
| 88 | +# Rather than writing out backup_label, try to recover the backup without |
| 89 | +# backup_label to demonstrate that recovery will not work correctly without it, |
| 90 | +# i.e. the canary table will be missing and the cluster will be corrupted. |
| 91 | +# Provide only the WAL segment that recovery will think it needs. |
| 92 | +# |
| 93 | +# The point of this test is to explicitly demonstrate that backup_label is |
| 94 | +# being used in a later test to get the correct recovery info. |
| 95 | +my $node_replica = PostgreSQL::Test::Cluster->new('replica_fail'); |
| 96 | +$node_replica->init_from_backup($node_primary, $backup_name); |
| 97 | +$node_replica->append_conf('postgresql.conf', "archive_mode = off"); |
| 98 | + |
| 99 | +my $canary_query = "select count(*) from pg_class where relname = 'canary'"; |
| 100 | + |
| 101 | +copy( |
| 102 | + $node_primary->archive_dir . "/$stop_segment_name", |
| 103 | + $node_replica->data_dir . "/pg_wal/$stop_segment_name" |
| 104 | +) or BAIL_OUT("unable to copy $stop_segment_name"); |
| 105 | + |
| 106 | +$node_replica->start; |
| 107 | + |
| 108 | +ok($node_replica->safe_psql('postgres', $canary_query) == 0, |
| 109 | + 'canary is missing'); |
| 110 | + |
| 111 | +# Check log to ensure that crash recovery was used as there is no |
| 112 | +# backup_label. |
| 113 | +ok( $node_replica->log_contains( |
| 114 | + 'database system was not properly shut down; automatic recovery in progress' |
| 115 | + ), |
| 116 | + 'verify backup recovery performed with crash recovery'); |
| 117 | + |
| 118 | +$node_replica->teardown_node; |
| 119 | +$node_replica->clean_node; |
| 120 | + |
| 121 | +# Save backup_label into the backup directory and recover using the primary's |
| 122 | +# archive. This time recovery will succeed and the canary table will be |
| 123 | +# present. |
| 124 | +open my $fh, ">>", "$backup_dir/backup_label" |
| 125 | + or die "could not open backup_label"; |
| 126 | +# Binary mode is required for Windows, as the backup_label parsing is not |
| 127 | +# able to cope with CRLFs. |
| 128 | +binmode $fh; |
| 129 | +print $fh $backup_label; |
| 130 | +close $fh; |
| 131 | + |
| 132 | +$node_replica = PostgreSQL::Test::Cluster->new('replica_success'); |
| 133 | +$node_replica->init_from_backup($node_primary, $backup_name, |
| 134 | + has_restoring => 1); |
| 135 | +$node_replica->start; |
| 136 | + |
| 137 | +ok($node_replica->safe_psql('postgres', $canary_query) == 1, |
| 138 | + 'canary is present'); |
| 139 | + |
| 140 | +# Check log to ensure that backup_label was used for recovery. |
| 141 | +ok($node_replica->log_contains('starting backup recovery with redo LSN'), |
| 142 | + 'verify backup recovery performed with backup_label'); |
| 143 | + |
| 144 | +done_testing(); |
0 commit comments