postgresql/src/bin/pg_basebackup/t/040_pg_createsubscriber.pl

365 lines
10 KiB
Perl

# Copyright (c) 2024, PostgreSQL Global Development Group
#
# Test using a standby server as the subscriber.
use strict;
use warnings FATAL => 'all';
use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;
program_help_ok('pg_createsubscriber');
program_version_ok('pg_createsubscriber');
program_options_handling_ok('pg_createsubscriber');
my $datadir = PostgreSQL::Test::Utils::tempdir;
#
# Test mandatory options
command_fails(['pg_createsubscriber'],
'no subscriber data directory specified');
command_fails(
[ 'pg_createsubscriber', '--pgdata', $datadir ],
'no publisher connection string specified');
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $datadir,
'--publisher-server', 'port=5432'
],
'no database name specified');
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $datadir,
'--publisher-server', 'port=5432',
'--database', 'pg1',
'--database', 'pg1'
],
'duplicate database name');
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $datadir,
'--publisher-server', 'port=5432',
'--publication', 'foo1',
'--publication', 'foo1',
'--database', 'pg1',
'--database', 'pg2'
],
'duplicate publication name');
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $datadir,
'--publisher-server', 'port=5432',
'--publication', 'foo1',
'--database', 'pg1',
'--database', 'pg2'
],
'wrong number of publication names');
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $datadir,
'--publisher-server', 'port=5432',
'--publication', 'foo1',
'--publication', 'foo2',
'--subscription', 'bar1',
'--database', 'pg1',
'--database', 'pg2'
],
'wrong number of subscription names');
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $datadir,
'--publisher-server', 'port=5432',
'--publication', 'foo1',
'--publication', 'foo2',
'--subscription', 'bar1',
'--subscription', 'bar2',
'--replication-slot', 'baz1',
'--database', 'pg1',
'--database', 'pg2'
],
'wrong number of replication slot names');
# Set up node P as primary
my $node_p = PostgreSQL::Test::Cluster->new('node_p');
$node_p->init(allows_streaming => 'logical');
$node_p->start;
# Set up node F as about-to-fail node
# Force it to initialize a new cluster instead of copying a
# previously initdb'd cluster. New cluster has a different system identifier so
# we can test if the target cluster is a copy of the source cluster.
my $node_f = PostgreSQL::Test::Cluster->new('node_f');
$node_f->init(force_initdb => 1, allows_streaming => 'logical');
# On node P
# - create databases
# - create test tables
# - insert a row
# - create a physical replication slot
$node_p->safe_psql(
'postgres', q(
CREATE DATABASE pg1;
CREATE DATABASE pg2;
));
$node_p->safe_psql('pg1', 'CREATE TABLE tbl1 (a text)');
$node_p->safe_psql('pg1', "INSERT INTO tbl1 VALUES('first row')");
$node_p->safe_psql('pg2', 'CREATE TABLE tbl2 (a text)');
my $slotname = 'physical_slot';
$node_p->safe_psql('pg2',
"SELECT pg_create_physical_replication_slot('$slotname')");
# Set up node S as standby linking to node P
$node_p->backup('backup_1');
my $node_s = PostgreSQL::Test::Cluster->new('node_s');
$node_s->init_from_backup($node_p, 'backup_1', has_streaming => 1);
$node_s->append_conf(
'postgresql.conf', qq[
primary_slot_name = '$slotname'
]);
$node_s->set_standby_mode();
$node_s->start;
# Set up node T as standby linking to node P then promote it
my $node_t = PostgreSQL::Test::Cluster->new('node_t');
$node_t->init_from_backup($node_p, 'backup_1', has_streaming => 1);
$node_t->set_standby_mode();
$node_t->start;
$node_t->promote;
$node_t->stop;
# Run pg_createsubscriber on a promoted server
command_fails(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_t->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_t->host,
'--subscriber-port', $node_t->port,
'--database', 'pg1',
'--database', 'pg2'
],
'target server is not in recovery');
# Run pg_createsubscriber when standby is running
command_fails(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_s->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_s->host,
'--subscriber-port', $node_s->port,
'--database', 'pg1',
'--database', 'pg2'
],
'standby is up and running');
# Run pg_createsubscriber on about-to-fail node F
command_fails(
[
'pg_createsubscriber', '--verbose',
'--pgdata', $node_f->data_dir,
'--publisher-server', $node_p->connstr('pg1'),
'--socket-directory', $node_f->host,
'--subscriber-port', $node_f->port,
'--database', 'pg1',
'--database', 'pg2'
],
'subscriber data directory is not a copy of the source database cluster');
# Set up node C as standby linking to node S
$node_s->backup('backup_2');
my $node_c = PostgreSQL::Test::Cluster->new('node_c');
$node_c->init_from_backup($node_s, 'backup_2', has_streaming => 1);
$node_c->adjust_conf('postgresql.conf', 'primary_slot_name', undef);
$node_c->set_standby_mode();
# Run pg_createsubscriber on node C (P -> S -> C)
command_fails(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_c->data_dir, '--publisher-server',
$node_s->connstr('pg1'),
'--socket-directory', $node_c->host,
'--subscriber-port', $node_c->port,
'--database', 'pg1',
'--database', 'pg2'
],
'primary server is in recovery');
# Insert another row on node P and wait node S to catch up
$node_p->safe_psql('pg1', "INSERT INTO tbl1 VALUES('second row')");
$node_p->wait_for_replay_catchup($node_s);
# Check some unmet conditions on node P
$node_p->append_conf('postgresql.conf', q{
wal_level = replica
max_replication_slots = 1
max_wal_senders = 1
max_worker_processes = 2
});
$node_p->restart;
$node_s->stop;
command_fails(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_s->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_s->host,
'--subscriber-port', $node_s->port,
'--database', 'pg1',
'--database', 'pg2'
],
'primary contains unmet conditions on node P');
# Restore default settings here but only apply it after testing standby. Some
# standby settings should not be a lower setting than on the primary.
$node_p->append_conf('postgresql.conf', q{
wal_level = logical
max_replication_slots = 10
max_wal_senders = 10
max_worker_processes = 8
});
# Check some unmet conditions on node S
$node_s->append_conf('postgresql.conf', q{
max_replication_slots = 1
max_logical_replication_workers = 1
max_worker_processes = 2
});
command_fails(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_s->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_s->host,
'--subscriber-port', $node_s->port,
'--database', 'pg1',
'--database', 'pg2'
],
'standby contains unmet conditions on node S');
$node_s->append_conf('postgresql.conf', q{
max_replication_slots = 10
max_logical_replication_workers = 4
max_worker_processes = 8
});
# Restore default settings on both servers
$node_p->restart;
# dry run mode on node S
command_ok(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_s->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_s->host,
'--subscriber-port', $node_s->port,
'--publication', 'pub1',
'--publication', 'pub2',
'--subscription', 'sub1',
'--subscription', 'sub2',
'--database', 'pg1',
'--database', 'pg2'
],
'run pg_createsubscriber --dry-run on node S');
# Check if node S is still a standby
$node_s->start;
is($node_s->safe_psql('postgres', 'SELECT pg_catalog.pg_is_in_recovery()'),
't', 'standby is in recovery');
$node_s->stop;
# pg_createsubscriber can run without --databases option
command_ok(
[
'pg_createsubscriber', '--verbose',
'--dry-run', '--pgdata',
$node_s->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_s->host,
'--subscriber-port', $node_s->port,
'--replication-slot', 'replslot1'
],
'run pg_createsubscriber without --databases');
# Run pg_createsubscriber on node S
command_ok(
[
'pg_createsubscriber', '--verbose',
'--verbose', '--pgdata',
$node_s->data_dir, '--publisher-server',
$node_p->connstr('pg1'),
'--socket-directory', $node_s->host,
'--subscriber-port', $node_s->port,
'--publication', 'pub1',
'--publication', 'Pub2',
'--replication-slot', 'replslot1',
'--replication-slot', 'replslot2',
'--database', 'pg1',
'--database', 'pg2'
],
'run pg_createsubscriber on node S');
# Confirm the physical replication slot has been removed
my $result = $node_p->safe_psql('pg1',
"SELECT count(*) FROM pg_replication_slots WHERE slot_name = '$slotname'"
);
is($result, qq(0),
'the physical replication slot used as primary_slot_name has been removed'
);
# Insert rows on P
$node_p->safe_psql('pg1', "INSERT INTO tbl1 VALUES('third row')");
$node_p->safe_psql('pg2', "INSERT INTO tbl2 VALUES('row 1')");
# Start subscriber
$node_s->start;
# Get subscription names
$result = $node_s->safe_psql(
'postgres', qq(
SELECT subname FROM pg_subscription WHERE subname ~ '^pg_createsubscriber_'
));
my @subnames = split("\n", $result);
# Wait subscriber to catch up
$node_s->wait_for_subscription_sync($node_p, $subnames[0]);
$node_s->wait_for_subscription_sync($node_p, $subnames[1]);
# Check result on database pg1
$result = $node_s->safe_psql('pg1', 'SELECT * FROM tbl1');
is( $result, qq(first row
second row
third row),
'logical replication works on database pg1');
# Check result on database pg2
$result = $node_s->safe_psql('pg2', 'SELECT * FROM tbl2');
is($result, qq(row 1), 'logical replication works on database pg2');
# Different system identifier?
my $sysid_p = $node_p->safe_psql('postgres',
'SELECT system_identifier FROM pg_control_system()');
my $sysid_s = $node_s->safe_psql('postgres',
'SELECT system_identifier FROM pg_control_system()');
ok($sysid_p != $sysid_s, 'system identifier was changed');
# clean up
$node_p->teardown_node;
$node_s->teardown_node;
$node_t->teardown_node;
$node_f->teardown_node;
done_testing();