|
| 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 | +$psql->query_safe("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 | +$psql->query_safe("select pg_switch_wal()"); |
| 59 | +$psql->query_safe("checkpoint"); |
| 60 | + |
| 61 | +# Copy pg_control last so it contains the new checkpoint. |
| 62 | +copy($node_primary->data_dir . '/global/pg_control', |
| 63 | + "$backup_dir/global/pg_control") |
| 64 | + or BAIL_OUT("unable to copy global/pg_control"); |
| 65 | + |
| 66 | +# Stop backup and get backup_label |
| 67 | +my $backup_label = |
| 68 | + $psql->query_safe("select labelfile from pg_backup_stop()"); |
| 69 | + |
| 70 | +$psql->quit; |
| 71 | + |
| 72 | +# Rather than writing out backup_label, try to recover the backup without |
| 73 | +# backup_label to demonstrate that recovery will not work correctly without it, |
| 74 | +# i.e. the canary table will be missing and the cluster will be corrupted. |
| 75 | +# Provide only the WAL segment that recovery will think it needs. |
| 76 | +# |
| 77 | +# The point of this test is to explicitly demonstrate that backup_label is |
| 78 | +# being used in a later test to get the correct recovery info. |
| 79 | +my $node_replica = PostgreSQL::Test::Cluster->new('replica_fail'); |
| 80 | +$node_replica->init_from_backup($node_primary, $backup_name); |
| 81 | +my $canary_query = "select count(*) from pg_class where relname = 'canary'"; |
| 82 | + |
| 83 | +copy( |
| 84 | + $node_primary->archive_dir . '/000000010000000000000003', |
| 85 | + $node_replica->data_dir . '/pg_wal/000000010000000000000003' |
| 86 | +) or BAIL_OUT("unable to copy 000000010000000000000003"); |
| 87 | + |
| 88 | +$node_replica->start; |
| 89 | + |
| 90 | +ok($node_replica->safe_psql('postgres', $canary_query) == 0, |
| 91 | + 'canary is missing'); |
| 92 | + |
| 93 | +# Check log to ensure that crash recovery was used as there is no |
| 94 | +# backup_label. |
| 95 | +ok( $node_replica->log_contains( |
| 96 | + 'database system was not properly shut down; automatic recovery in progress' |
| 97 | + ), |
| 98 | + 'verify backup recovery performed with crash recovery'); |
| 99 | + |
| 100 | +$node_replica->teardown_node; |
| 101 | +$node_replica->clean_node; |
| 102 | + |
| 103 | +# Save backup_label into the backup directory and recover using the primary's |
| 104 | +# archive. This time recovery will succeed and the canary table will be |
| 105 | +# present. |
| 106 | +append_to_file("$backup_dir/backup_label", $backup_label); |
| 107 | + |
| 108 | +$node_replica = PostgreSQL::Test::Cluster->new('replica_success'); |
| 109 | +$node_replica->init_from_backup($node_primary, $backup_name, |
| 110 | + has_restoring => 1); |
| 111 | +$node_replica->start; |
| 112 | + |
| 113 | +ok($node_replica->safe_psql('postgres', $canary_query) == 1, |
| 114 | + 'canary is present'); |
| 115 | + |
| 116 | +# Check log to ensure that backup_label was used for recovery. |
| 117 | +ok($node_replica->log_contains('starting backup recovery with redo LSN'), |
| 118 | + 'verify backup recovery performed with backup_label'); |
| 119 | + |
| 120 | +done_testing(); |
0 commit comments