From b67248cd5911ae553cd684d2130dffee59afacaf Mon Sep 17 00:00:00 2001 From: Bouke van der Bijl Date: Thu, 11 Dec 2025 11:45:26 +0100 Subject: [PATCH] nixos/postgresql: don't create postgresql-setup if no setup is needed Also add postgresql replication test Fixes #469859 --- .../modules/services/databases/postgresql.nix | 6 + nixos/tests/postgresql/default.nix | 1 + .../postgresql/postgresql-replication.nix | 125 ++++++++++++++++++ pkgs/servers/sql/postgresql/generic.nix | 1 + 4 files changed, 133 insertions(+) create mode 100644 nixos/tests/postgresql/postgresql-replication.nix diff --git a/nixos/modules/services/databases/postgresql.nix b/nixos/modules/services/databases/postgresql.nix index f4b309fbe4b6..d51f5d56c302 100644 --- a/nixos/modules/services/databases/postgresql.nix +++ b/nixos/modules/services/databases/postgresql.nix @@ -911,6 +911,12 @@ in # Wait for PostgreSQL to be ready to accept connections. script = '' + # If we're in standby mode, don't perform any setup + if [[ -f "${cfg.dataDir}/standby.signal" ]]; then + echo "Skipping setup because PostgreSQL is in standby mode" + exit 0 + fi + check-connection() { psql -d postgres -v ON_ERROR_STOP=1 <<-' EOF' SELECT pg_is_in_recovery() \gset diff --git a/nixos/tests/postgresql/default.nix b/nixos/tests/postgresql/default.nix index f7266c2e9db2..a8b03a29280b 100644 --- a/nixos/tests/postgresql/default.nix +++ b/nixos/tests/postgresql/default.nix @@ -31,6 +31,7 @@ in # postgresql postgresql = importWithArgs ./postgresql.nix; postgresql-jit = importWithArgs ./postgresql-jit.nix; + postgresql-replication = importWithArgs ./postgresql-replication.nix; postgresql-wal-receiver = importWithArgs ./postgresql-wal-receiver.nix; postgresql-tls-client-cert = importWithArgs ./postgresql-tls-client-cert.nix; diff --git a/nixos/tests/postgresql/postgresql-replication.nix b/nixos/tests/postgresql/postgresql-replication.nix new file mode 100644 index 000000000000..b828c2d7bb49 --- /dev/null +++ b/nixos/tests/postgresql/postgresql-replication.nix @@ -0,0 +1,125 @@ +{ + pkgs, + makeTest, + genTests, +}: + +let + inherit (pkgs) lib; + + makeTestFor = + package: + makeTest { + name = "postgresql-replication-${package.name}"; + meta.maintainers = with lib.maintainers; [ bouk ]; + + nodes = { + primary = + { ... }: + { + services.postgresql = { + inherit package; + enable = true; + enableTCPIP = true; + settings = { + wal_level = "replica"; + max_wal_senders = 10; + max_replication_slots = 10; + }; + authentication = '' + local replication postgres peer + host replication replication all trust + ''; + ensureUsers = [ + { + name = "replication"; + ensureClauses.replication = true; + } + ]; + }; + networking.firewall.allowedTCPPorts = [ 5432 ]; + }; + + replica = + { nodes, ... }: + { + services.postgresql = { + inherit package; + enable = true; + settings = { + hot_standby = "on"; + primary_conninfo = "host=${nodes.primary.networking.primaryIPAddress} user=replication"; + primary_slot_name = "replica_slot"; + }; + }; + }; + }; + + testScript = '' + start_all() + primary.wait_for_unit("postgresql.target") + + primary.succeed( + "sudo -u postgres psql -c \"SELECT * FROM pg_create_physical_replication_slot('replica_slot');\"" + ) + + primary.succeed( + "sudo -u postgres pg_basebackup -D /tmp/basebackup -S replica_slot -X stream" + ) + primary.succeed("tar -C /tmp -cf /tmp/shared/basebackup.tar basebackup") + + replica.wait_for_unit("postgresql.target") + replica.succeed("systemctl stop postgresql") + + replica_data_dir = "/var/lib/postgresql/${package.psqlSchema}" + replica.succeed(f"rm -rf {replica_data_dir}") + replica.succeed(f"mkdir -p {replica_data_dir}") + replica.succeed(f"tar -C {replica_data_dir} --strip-components=1 -xf /tmp/shared/basebackup.tar") + replica.succeed(f"touch {replica_data_dir}/standby.signal") + replica.succeed(f"chown -R postgres:postgres {replica_data_dir}") + replica.succeed(f"chmod 700 {replica_data_dir}") + + replica.succeed("systemctl start postgresql") + replica.wait_for_unit("postgresql.target") + + replica.wait_until_succeeds( + "sudo -u postgres psql -tAc 'SELECT pg_is_in_recovery();' | grep t" + ) + + primary.succeed( + "sudo -u postgres psql -c 'CREATE TABLE test_replication (id serial PRIMARY KEY, data text);'" + ) + primary.succeed( + "sudo -u postgres psql -c \"INSERT INTO test_replication (data) VALUES ('hello');\"" + ) + + replica.wait_until_succeeds( + "sudo -u postgres psql -c 'SELECT * FROM test_replication;' | grep hello", + timeout=30 + ) + + with subtest("Verify replica is in recovery mode"): + result = replica.succeed("sudo -u postgres psql -tAc 'SELECT pg_is_in_recovery();'") + t.assertEqual(result.strip(), "t") + + with subtest("Verify replication slot is active"): + result = primary.succeed( + "sudo -u postgres psql -tAc \"SELECT active FROM pg_replication_slots WHERE slot_name = 'replica_slot';\"" + ) + t.assertEqual(result.strip(), "t") + + with subtest("Insert more data and verify replication"): + primary.succeed( + "sudo -u postgres psql -c \"INSERT INTO test_replication (data) VALUES ('world');\"" + ) + replica.wait_until_succeeds( + "sudo -u postgres psql -c 'SELECT * FROM test_replication;' | grep world", + timeout=30 + ) + + primary.shutdown() + replica.shutdown() + ''; + }; +in +genTests { inherit makeTestFor; } diff --git a/pkgs/servers/sql/postgresql/generic.nix b/pkgs/servers/sql/postgresql/generic.nix index 55627971fd80..7337a80fa284 100644 --- a/pkgs/servers/sql/postgresql/generic.nix +++ b/pkgs/servers/sql/postgresql/generic.nix @@ -612,6 +612,7 @@ let tests = { postgresql = nixosTests.postgresql.postgresql.passthru.override finalAttrs.finalPackage; + postgresql-replication = nixosTests.postgresql.postgresql-replication.passthru.override finalAttrs.finalPackage; postgresql-tls-client-cert = nixosTests.postgresql.postgresql-tls-client-cert.passthru.override finalAttrs.finalPackage; postgresql-wal-receiver = nixosTests.postgresql.postgresql-wal-receiver.passthru.override finalAttrs.finalPackage; pkg-config = testers.testMetaPkgConfig finalAttrs.finalPackage;