mirror of
https://github.com/NixOS/nixpkgs.git
synced 2026-06-05 21:03:40 +00:00
nixos/tests/postgresql: migrate from handleTest to runTest
See: https://github.com/NixOS/nixpkgs/issues/386873
This commit is contained in:
@@ -1283,7 +1283,7 @@ in
|
||||
{ };
|
||||
postfix-tlspol = runTest ./postfix-tlspol.nix;
|
||||
postgres-websockets = runTest ./postgres-websockets.nix;
|
||||
postgresql = handleTest ./postgresql { };
|
||||
postgresql = import ./postgresql { inherit runTest pkgs; };
|
||||
postgrest = runTest ./postgrest.nix;
|
||||
power-profiles-daemon = runTest ./power-profiles-daemon.nix;
|
||||
powerdns = runTest ./powerdns.nix;
|
||||
|
||||
@@ -1,114 +1,115 @@
|
||||
{
|
||||
pkgs,
|
||||
makeTest,
|
||||
runTest,
|
||||
genTests,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
|
||||
makeTestFor =
|
||||
package:
|
||||
makeTest {
|
||||
name = "postgresql_anonymizer-${package.name}";
|
||||
meta.maintainers = [
|
||||
lib.maintainers.leona
|
||||
lib.maintainers.osnyx
|
||||
];
|
||||
runTest (
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
name = "postgresql_anonymizer-${package.name}";
|
||||
meta.maintainers = [
|
||||
lib.maintainers.leona
|
||||
lib.maintainers.osnyx
|
||||
];
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = [ (pkgs.pg-dump-anon.override { postgresql = package; }) ];
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
extensions = ps: [ ps.anonymizer ];
|
||||
settings.shared_preload_libraries = [ "anon" ];
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
environment.systemPackages = [ (pkgs.pg-dump-anon.override { postgresql = package; }) ];
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
extensions = ps: [ ps.anonymizer ];
|
||||
settings.shared_preload_libraries = [ "anon" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
testScript = ''
|
||||
start_all()
|
||||
machine.wait_for_unit("multi-user.target")
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
|
||||
with subtest("Setup"):
|
||||
machine.succeed("sudo -u postgres psql --command 'create database demo'")
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql -d demo -f ${pkgs.writeText "init.sql" ''
|
||||
create extension anon cascade;
|
||||
select anon.init();
|
||||
create table player(id serial, name text, points int);
|
||||
insert into player(id,name,points) values (1,'Foo', 23);
|
||||
insert into player(id,name,points) values (2,'Bar',42);
|
||||
security label for anon on column player.name is 'MASKED WITH FUNCTION anon.fake_last_name()';
|
||||
security label for anon on column player.points is 'MASKED WITH VALUE NULL';
|
||||
''}"
|
||||
)
|
||||
with subtest("Setup"):
|
||||
machine.succeed("sudo -u postgres psql --command 'create database demo'")
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql -d demo -f ${pkgs.writeText "init.sql" ''
|
||||
create extension anon cascade;
|
||||
select anon.init();
|
||||
create table player(id serial, name text, points int);
|
||||
insert into player(id,name,points) values (1,'Foo', 23);
|
||||
insert into player(id,name,points) values (2,'Bar',42);
|
||||
security label for anon on column player.name is 'MASKED WITH FUNCTION anon.fake_last_name()';
|
||||
security label for anon on column player.points is 'MASKED WITH VALUE NULL';
|
||||
''}"
|
||||
)
|
||||
|
||||
def get_player_table_contents():
|
||||
return [
|
||||
x.split(',') for x in machine.succeed("sudo -u postgres psql -d demo --csv --command 'select * from player'").splitlines()[1:]
|
||||
]
|
||||
def get_player_table_contents():
|
||||
return [
|
||||
x.split(',') for x in machine.succeed("sudo -u postgres psql -d demo --csv --command 'select * from player'").splitlines()[1:]
|
||||
]
|
||||
|
||||
def check_anonymized_row(row, id, original_name):
|
||||
t.assertEqual(row[0], id)
|
||||
t.assertNotEqual(row[1], original_name)
|
||||
t.assertFalse(bool(row[2]))
|
||||
def check_anonymized_row(row, id, original_name):
|
||||
t.assertEqual(row[0], id)
|
||||
t.assertNotEqual(row[1], original_name)
|
||||
t.assertFalse(bool(row[2]))
|
||||
|
||||
def find_xsv_in_dump(dump, sep=','):
|
||||
"""
|
||||
Expecting to find a CSV (for pg_dump_anon) or TSV (for pg_dump) structure, looking like
|
||||
def find_xsv_in_dump(dump, sep=','):
|
||||
"""
|
||||
Expecting to find a CSV (for pg_dump_anon) or TSV (for pg_dump) structure, looking like
|
||||
|
||||
COPY public.player ...
|
||||
1,Shields,
|
||||
2,Salazar,
|
||||
\\.
|
||||
COPY public.player ...
|
||||
1,Shields,
|
||||
2,Salazar,
|
||||
\\.
|
||||
|
||||
in the given dump (the commas are tabs in case of pg_dump).
|
||||
Extract the CSV lines and split by `sep`.
|
||||
"""
|
||||
in the given dump (the commas are tabs in case of pg_dump).
|
||||
Extract the CSV lines and split by `sep`.
|
||||
"""
|
||||
|
||||
try:
|
||||
from itertools import dropwhile, takewhile
|
||||
return [x.split(sep) for x in list(takewhile(
|
||||
lambda x: x != "\\.",
|
||||
dropwhile(
|
||||
lambda x: not x.startswith("COPY public.player"),
|
||||
dump.splitlines()
|
||||
)
|
||||
))[1:]]
|
||||
except:
|
||||
print(f"Dump to process: {dump}")
|
||||
raise
|
||||
try:
|
||||
from itertools import dropwhile, takewhile
|
||||
return [x.split(sep) for x in list(takewhile(
|
||||
lambda x: x != "\\.",
|
||||
dropwhile(
|
||||
lambda x: not x.startswith("COPY public.player"),
|
||||
dump.splitlines()
|
||||
)
|
||||
))[1:]]
|
||||
except:
|
||||
print(f"Dump to process: {dump}")
|
||||
raise
|
||||
|
||||
def check_original_data(output):
|
||||
t.assertEqual(output[0], ["1", "Foo", "23"])
|
||||
t.assertEqual(output[1], ["2", "Bar", "42"])
|
||||
def check_original_data(output):
|
||||
t.assertEqual(output[0], ["1", "Foo", "23"])
|
||||
t.assertEqual(output[1], ["2", "Bar", "42"])
|
||||
|
||||
def check_anonymized_rows(output):
|
||||
check_anonymized_row(output[0], '1', 'Foo')
|
||||
check_anonymized_row(output[1], '2', 'Bar')
|
||||
def check_anonymized_rows(output):
|
||||
check_anonymized_row(output[0], '1', 'Foo')
|
||||
check_anonymized_row(output[1], '2', 'Bar')
|
||||
|
||||
with subtest("Check initial state"):
|
||||
check_original_data(get_player_table_contents())
|
||||
with subtest("Check initial state"):
|
||||
check_original_data(get_player_table_contents())
|
||||
|
||||
with subtest("Anonymous dumps"):
|
||||
check_original_data(find_xsv_in_dump(
|
||||
machine.succeed("sudo -u postgres pg_dump demo"),
|
||||
sep='\t'
|
||||
))
|
||||
check_anonymized_rows(find_xsv_in_dump(
|
||||
machine.succeed("sudo -u postgres pg_dump_anon -U postgres -h /run/postgresql -d demo"),
|
||||
sep=','
|
||||
))
|
||||
with subtest("Anonymous dumps"):
|
||||
check_original_data(find_xsv_in_dump(
|
||||
machine.succeed("sudo -u postgres pg_dump demo"),
|
||||
sep='\t'
|
||||
))
|
||||
check_anonymized_rows(find_xsv_in_dump(
|
||||
machine.succeed("sudo -u postgres pg_dump_anon -U postgres -h /run/postgresql -d demo"),
|
||||
sep=','
|
||||
))
|
||||
|
||||
with subtest("Anonymize"):
|
||||
machine.succeed("sudo -u postgres psql -d demo --command 'select anon.anonymize_database();'")
|
||||
check_anonymized_rows(get_player_table_contents())
|
||||
'';
|
||||
};
|
||||
with subtest("Anonymize"):
|
||||
machine.succeed("sudo -u postgres psql -d demo --command 'select anon.anonymize_database();'")
|
||||
check_anonymized_rows(get_player_table_contents())
|
||||
'';
|
||||
}
|
||||
);
|
||||
in
|
||||
genTests {
|
||||
inherit makeTestFor;
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
{
|
||||
system ? builtins.currentSystem,
|
||||
config ? { },
|
||||
pkgs ? import ../../.. { inherit system config; },
|
||||
runTest,
|
||||
pkgs,
|
||||
}:
|
||||
|
||||
with import ../../lib/testing-python.nix { inherit system pkgs; };
|
||||
|
||||
let
|
||||
inherit (pkgs.lib)
|
||||
recurseIntoAttrs
|
||||
@@ -25,7 +22,12 @@ let
|
||||
}
|
||||
);
|
||||
|
||||
importWithArgs = path: import path { inherit pkgs makeTest genTests; };
|
||||
importWithArgs =
|
||||
path:
|
||||
import path {
|
||||
inherit runTest genTests;
|
||||
inherit (pkgs) lib;
|
||||
};
|
||||
in
|
||||
{
|
||||
# postgresql
|
||||
|
||||
@@ -1,51 +1,52 @@
|
||||
{
|
||||
pkgs,
|
||||
makeTest,
|
||||
runTest,
|
||||
genTests,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
|
||||
makeTestFor =
|
||||
package:
|
||||
makeTest {
|
||||
name = "pgjwt-${package.name}";
|
||||
meta = with lib.maintainers; {
|
||||
maintainers = [
|
||||
spinus
|
||||
];
|
||||
};
|
||||
|
||||
nodes.master =
|
||||
{ ... }:
|
||||
{
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
extensions =
|
||||
ps: with ps; [
|
||||
pgjwt
|
||||
pgtap
|
||||
];
|
||||
};
|
||||
runTest (
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
name = "pgjwt-${package.name}";
|
||||
meta = with lib.maintainers; {
|
||||
maintainers = [
|
||||
spinus
|
||||
];
|
||||
};
|
||||
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
sqlSU = "${nodes.master.services.postgresql.superUser}";
|
||||
pgProve = "${pkgs.perlPackages.TAPParserSourceHandlerpgTAP}";
|
||||
inherit (nodes.master.services.postgresql.package.pkgs) pgjwt;
|
||||
in
|
||||
''
|
||||
start_all()
|
||||
master.wait_for_unit("postgresql.target")
|
||||
master.succeed(
|
||||
"${pkgs.sudo}/bin/sudo -u ${sqlSU} ${pgProve}/bin/pg_prove -d postgres -v -f ${pgjwt.src}/test.sql"
|
||||
)
|
||||
'';
|
||||
};
|
||||
nodes.master =
|
||||
{ ... }:
|
||||
{
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
extensions =
|
||||
ps: with ps; [
|
||||
pgjwt
|
||||
pgtap
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
{ nodes, ... }:
|
||||
let
|
||||
sqlSU = "${nodes.master.services.postgresql.superUser}";
|
||||
pgProve = "${pkgs.perlPackages.TAPParserSourceHandlerpgTAP}";
|
||||
inherit (nodes.master.services.postgresql.package.pkgs) pgjwt;
|
||||
in
|
||||
''
|
||||
start_all()
|
||||
master.wait_for_unit("postgresql.target")
|
||||
master.succeed(
|
||||
"${pkgs.sudo}/bin/sudo -u ${sqlSU} ${pgProve}/bin/pg_prove -d postgres -v -f ${pgjwt.src}/test.sql"
|
||||
)
|
||||
'';
|
||||
}
|
||||
);
|
||||
in
|
||||
genTests {
|
||||
inherit makeTestFor;
|
||||
|
||||
@@ -1,53 +1,54 @@
|
||||
{
|
||||
pkgs,
|
||||
makeTest,
|
||||
runTest,
|
||||
genTests,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
|
||||
makeTestFor =
|
||||
package:
|
||||
makeTest {
|
||||
name = "postgresql-jit-${package.name}";
|
||||
meta.maintainers = with lib.maintainers; [ ma27 ];
|
||||
runTest (
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
name = "postgresql-jit-${package.name}";
|
||||
meta.maintainers = with lib.maintainers; [ ma27 ];
|
||||
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
enableJIT = true;
|
||||
initialScript = pkgs.writeText "init.sql" ''
|
||||
create table demo (id int);
|
||||
insert into demo (id) select generate_series(1, 5);
|
||||
'';
|
||||
nodes.machine =
|
||||
{ pkgs, ... }:
|
||||
{
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
enableJIT = true;
|
||||
initialScript = pkgs.writeText "init.sql" ''
|
||||
create table demo (id int);
|
||||
insert into demo (id) select generate_series(1, 5);
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.start()
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
testScript = ''
|
||||
machine.start()
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
|
||||
with subtest("JIT is enabled"):
|
||||
machine.succeed("sudo -u postgres psql <<<'show jit;' | grep 'on'")
|
||||
with subtest("JIT is enabled"):
|
||||
machine.succeed("sudo -u postgres psql <<<'show jit;' | grep 'on'")
|
||||
|
||||
with subtest("Test JIT works fine"):
|
||||
output = machine.succeed(
|
||||
"cat ${pkgs.writeText "test.sql" ''
|
||||
set jit_above_cost = 1;
|
||||
EXPLAIN ANALYZE SELECT CONCAT('jit result = ', SUM(id)) FROM demo;
|
||||
SELECT CONCAT('jit result = ', SUM(id)) from demo;
|
||||
''} | sudo -u postgres psql"
|
||||
)
|
||||
t.assertIn("JIT:", output)
|
||||
t.assertIn("jit result = 15", output)
|
||||
with subtest("Test JIT works fine"):
|
||||
output = machine.succeed(
|
||||
"cat ${pkgs.writeText "test.sql" ''
|
||||
set jit_above_cost = 1;
|
||||
EXPLAIN ANALYZE SELECT CONCAT('jit result = ', SUM(id)) FROM demo;
|
||||
SELECT CONCAT('jit result = ', SUM(id)) from demo;
|
||||
''} | sudo -u postgres psql"
|
||||
)
|
||||
t.assertIn("JIT:", output)
|
||||
t.assertIn("jit result = 15", output)
|
||||
|
||||
machine.shutdown()
|
||||
'';
|
||||
};
|
||||
machine.shutdown()
|
||||
'';
|
||||
}
|
||||
);
|
||||
in
|
||||
genTests {
|
||||
inherit makeTestFor;
|
||||
|
||||
@@ -1,125 +1,127 @@
|
||||
{
|
||||
pkgs,
|
||||
makeTest,
|
||||
runTest,
|
||||
genTests,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
|
||||
makeTestFor =
|
||||
package:
|
||||
makeTest {
|
||||
name = "postgresql-replication-${package.name}";
|
||||
meta.maintainers = with lib.maintainers; [ bouk ];
|
||||
runTest (
|
||||
{ lib, ... }:
|
||||
{
|
||||
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;
|
||||
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;
|
||||
}
|
||||
];
|
||||
};
|
||||
authentication = ''
|
||||
local replication postgres peer
|
||||
host replication replication all trust
|
||||
'';
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "replication";
|
||||
ensureClauses.replication = true;
|
||||
}
|
||||
];
|
||||
networking.firewall.allowedTCPPorts = [ 5432 ];
|
||||
};
|
||||
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";
|
||||
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")
|
||||
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 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")
|
||||
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.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_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.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"
|
||||
)
|
||||
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');\""
|
||||
)
|
||||
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
|
||||
)
|
||||
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 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("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
|
||||
)
|
||||
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()
|
||||
'';
|
||||
};
|
||||
primary.shutdown()
|
||||
replica.shutdown()
|
||||
'';
|
||||
}
|
||||
);
|
||||
in
|
||||
genTests { inherit makeTestFor; }
|
||||
|
||||
@@ -1,145 +1,147 @@
|
||||
{
|
||||
pkgs,
|
||||
makeTest,
|
||||
runTest,
|
||||
genTests,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
|
||||
runWithOpenSSL =
|
||||
file: cmd:
|
||||
pkgs.runCommand file {
|
||||
buildInputs = [ pkgs.openssl ];
|
||||
} cmd;
|
||||
caKey = runWithOpenSSL "ca.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out";
|
||||
caCert = runWithOpenSSL "ca.crt" ''
|
||||
openssl req -new -x509 -sha256 -key ${caKey} -out $out -subj "/CN=test.example" -days 36500
|
||||
'';
|
||||
serverKey = runWithOpenSSL "server.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out";
|
||||
serverKeyPath = "/var/lib/postgresql";
|
||||
serverCert = runWithOpenSSL "server.crt" ''
|
||||
openssl req -new -sha256 -key ${serverKey} -out server.csr -subj "/CN=db.test.example"
|
||||
openssl x509 -req -in server.csr -CA ${caCert} -CAkey ${caKey} \
|
||||
-CAcreateserial -out $out -days 36500 -sha256
|
||||
'';
|
||||
clientKey = runWithOpenSSL "client.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out";
|
||||
clientCert = runWithOpenSSL "client.crt" ''
|
||||
openssl req -new -sha256 -key ${clientKey} -out client.csr -subj "/CN=test"
|
||||
openssl x509 -req -in client.csr -CA ${caCert} -CAkey ${caKey} \
|
||||
-CAcreateserial -out $out -days 36500 -sha256
|
||||
'';
|
||||
clientKeyPath = "/root";
|
||||
|
||||
makeTestFor =
|
||||
package:
|
||||
makeTest {
|
||||
name = "postgresql-tls-client-cert-${package.name}";
|
||||
meta.maintainers = with lib.maintainers; [ erictapen ];
|
||||
runTest (
|
||||
{ lib, pkgs, ... }:
|
||||
let
|
||||
runWithOpenSSL =
|
||||
file: cmd:
|
||||
pkgs.runCommand file {
|
||||
buildInputs = [ pkgs.openssl ];
|
||||
} cmd;
|
||||
caKey = runWithOpenSSL "ca.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out";
|
||||
caCert = runWithOpenSSL "ca.crt" ''
|
||||
openssl req -new -x509 -sha256 -key ${caKey} -out $out -subj "/CN=test.example" -days 36500
|
||||
'';
|
||||
serverKey = runWithOpenSSL "server.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out";
|
||||
serverKeyPath = "/var/lib/postgresql";
|
||||
serverCert = runWithOpenSSL "server.crt" ''
|
||||
openssl req -new -sha256 -key ${serverKey} -out server.csr -subj "/CN=db.test.example"
|
||||
openssl x509 -req -in server.csr -CA ${caCert} -CAkey ${caKey} \
|
||||
-CAcreateserial -out $out -days 36500 -sha256
|
||||
'';
|
||||
clientKey = runWithOpenSSL "client.key" "openssl ecparam -name prime256v1 -genkey -noout -out $out";
|
||||
clientCert = runWithOpenSSL "client.crt" ''
|
||||
openssl req -new -sha256 -key ${clientKey} -out client.csr -subj "/CN=test"
|
||||
openssl x509 -req -in client.csr -CA ${caCert} -CAkey ${caKey} \
|
||||
-CAcreateserial -out $out -days 36500 -sha256
|
||||
'';
|
||||
clientKeyPath = "/root";
|
||||
in
|
||||
{
|
||||
name = "postgresql-tls-client-cert-${package.name}";
|
||||
meta.maintainers = with lib.maintainers; [ erictapen ];
|
||||
|
||||
nodes.server =
|
||||
{ ... }:
|
||||
{
|
||||
systemd.services.create-keys = {
|
||||
wantedBy = [ "postgresql.target" ];
|
||||
nodes.server =
|
||||
{ ... }:
|
||||
{
|
||||
systemd.services.create-keys = {
|
||||
wantedBy = [ "postgresql.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
|
||||
script = ''
|
||||
mkdir -p '${serverKeyPath}'
|
||||
cp '${serverKey}' '${serverKeyPath}/server.key'
|
||||
chown postgres:postgres '${serverKeyPath}/server.key'
|
||||
chmod 600 '${serverKeyPath}/server.key'
|
||||
'';
|
||||
};
|
||||
|
||||
script = ''
|
||||
mkdir -p '${serverKeyPath}'
|
||||
cp '${serverKey}' '${serverKeyPath}/server.key'
|
||||
chown postgres:postgres '${serverKeyPath}/server.key'
|
||||
chmod 600 '${serverKeyPath}/server.key'
|
||||
'';
|
||||
};
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
enableTCPIP = true;
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "test";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
ensureDatabases = [ "test" ];
|
||||
settings = {
|
||||
ssl = "on";
|
||||
ssl_ca_file = toString caCert;
|
||||
ssl_cert_file = toString serverCert;
|
||||
ssl_key_file = "${serverKeyPath}/server.key";
|
||||
};
|
||||
authentication = ''
|
||||
hostssl test test ::/0 cert clientcert=verify-full
|
||||
'';
|
||||
};
|
||||
networking = {
|
||||
interfaces.eth1 = {
|
||||
ipv6.addresses = [
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
enableTCPIP = true;
|
||||
ensureUsers = [
|
||||
{
|
||||
address = "fc00::1";
|
||||
prefixLength = 120;
|
||||
name = "test";
|
||||
ensureDBOwnership = true;
|
||||
}
|
||||
];
|
||||
ensureDatabases = [ "test" ];
|
||||
settings = {
|
||||
ssl = "on";
|
||||
ssl_ca_file = toString caCert;
|
||||
ssl_cert_file = toString serverCert;
|
||||
ssl_key_file = "${serverKeyPath}/server.key";
|
||||
};
|
||||
authentication = ''
|
||||
hostssl test test ::/0 cert clientcert=verify-full
|
||||
'';
|
||||
};
|
||||
firewall.allowedTCPPorts = [ 5432 ];
|
||||
};
|
||||
};
|
||||
|
||||
nodes.client =
|
||||
{ ... }:
|
||||
{
|
||||
systemd.services.create-keys = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
|
||||
script = ''
|
||||
mkdir -p '${clientKeyPath}'
|
||||
cp '${clientKey}' '${clientKeyPath}/client.key'
|
||||
chown root:root '${clientKeyPath}/client.key'
|
||||
chmod 600 '${clientKeyPath}/client.key'
|
||||
'';
|
||||
};
|
||||
environment = {
|
||||
variables = {
|
||||
PGHOST = "db.test.example";
|
||||
PGPORT = "5432";
|
||||
PGDATABASE = "test";
|
||||
PGUSER = "test";
|
||||
PGSSLMODE = "verify-full";
|
||||
PGSSLCERT = clientCert;
|
||||
PGSSLKEY = "${clientKeyPath}/client.key";
|
||||
PGSSLROOTCERT = caCert;
|
||||
};
|
||||
systemPackages = [ package ];
|
||||
};
|
||||
networking = {
|
||||
interfaces.eth1 = {
|
||||
ipv6.addresses = [
|
||||
{
|
||||
address = "fc00::2";
|
||||
prefixLength = 120;
|
||||
}
|
||||
];
|
||||
};
|
||||
hosts = {
|
||||
"fc00::1" = [ "db.test.example" ];
|
||||
networking = {
|
||||
interfaces.eth1 = {
|
||||
ipv6.addresses = [
|
||||
{
|
||||
address = "fc00::1";
|
||||
prefixLength = 120;
|
||||
}
|
||||
];
|
||||
};
|
||||
firewall.allowedTCPPorts = [ 5432 ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
server.wait_for_unit("multi-user.target")
|
||||
client.wait_for_unit("multi-user.target")
|
||||
client.succeed("psql -c \"SELECT 1;\"")
|
||||
'';
|
||||
};
|
||||
nodes.client =
|
||||
{ ... }:
|
||||
{
|
||||
systemd.services.create-keys = {
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "oneshot";
|
||||
RemainAfterExit = true;
|
||||
};
|
||||
|
||||
script = ''
|
||||
mkdir -p '${clientKeyPath}'
|
||||
cp '${clientKey}' '${clientKeyPath}/client.key'
|
||||
chown root:root '${clientKeyPath}/client.key'
|
||||
chmod 600 '${clientKeyPath}/client.key'
|
||||
'';
|
||||
};
|
||||
environment = {
|
||||
variables = {
|
||||
PGHOST = "db.test.example";
|
||||
PGPORT = "5432";
|
||||
PGDATABASE = "test";
|
||||
PGUSER = "test";
|
||||
PGSSLMODE = "verify-full";
|
||||
PGSSLCERT = clientCert;
|
||||
PGSSLKEY = "${clientKeyPath}/client.key";
|
||||
PGSSLROOTCERT = caCert;
|
||||
};
|
||||
systemPackages = [ package ];
|
||||
};
|
||||
networking = {
|
||||
interfaces.eth1 = {
|
||||
ipv6.addresses = [
|
||||
{
|
||||
address = "fc00::2";
|
||||
prefixLength = 120;
|
||||
}
|
||||
];
|
||||
};
|
||||
hosts = {
|
||||
"fc00::1" = [ "db.test.example" ];
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
server.wait_for_unit("multi-user.target")
|
||||
client.wait_for_unit("multi-user.target")
|
||||
client.succeed("psql -c \"SELECT 1;\"")
|
||||
'';
|
||||
}
|
||||
);
|
||||
in
|
||||
genTests { inherit makeTestFor; }
|
||||
|
||||
@@ -1,111 +1,112 @@
|
||||
{
|
||||
pkgs,
|
||||
makeTest,
|
||||
runTest,
|
||||
genTests,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
|
||||
makeTestFor =
|
||||
package:
|
||||
let
|
||||
postgresqlDataDir = "/var/lib/postgresql/${package.psqlSchema}";
|
||||
replicationUser = "wal_receiver_user";
|
||||
replicationSlot = "wal_receiver_slot";
|
||||
replicationConn = "postgresql://${replicationUser}@localhost";
|
||||
baseBackupDir = "/var/cache/wals/pg_basebackup";
|
||||
walBackupDir = "/var/cache/wals/pg_wal";
|
||||
recoveryFile = pkgs.writeTextDir "recovery.signal" "";
|
||||
in
|
||||
makeTest {
|
||||
name = "postgresql-wal-receiver-${package.name}";
|
||||
meta.maintainers = with lib.maintainers; [ euxane ];
|
||||
runTest (
|
||||
{ lib, pkgs, ... }:
|
||||
let
|
||||
postgresqlDataDir = "/var/lib/postgresql/${package.psqlSchema}";
|
||||
replicationUser = "wal_receiver_user";
|
||||
replicationSlot = "wal_receiver_slot";
|
||||
replicationConn = "postgresql://${replicationUser}@localhost";
|
||||
baseBackupDir = "/var/cache/wals/pg_basebackup";
|
||||
walBackupDir = "/var/cache/wals/pg_wal";
|
||||
recoveryFile = pkgs.writeTextDir "recovery.signal" "";
|
||||
in
|
||||
{
|
||||
name = "postgresql-wal-receiver-${package.name}";
|
||||
meta.maintainers = with lib.maintainers; [ euxane ];
|
||||
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/cache/wals 0750 postgres postgres - -"
|
||||
];
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{
|
||||
systemd.tmpfiles.rules = [
|
||||
"d /var/cache/wals 0750 postgres postgres - -"
|
||||
];
|
||||
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
settings = {
|
||||
max_replication_slots = 10;
|
||||
max_wal_senders = 10;
|
||||
recovery_end_command = "touch recovery.done";
|
||||
restore_command = "cp ${walBackupDir}/%f %p";
|
||||
wal_level = "archive"; # alias for replica on pg >= 9.6
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
settings = {
|
||||
max_replication_slots = 10;
|
||||
max_wal_senders = 10;
|
||||
recovery_end_command = "touch recovery.done";
|
||||
restore_command = "cp ${walBackupDir}/%f %p";
|
||||
wal_level = "archive"; # alias for replica on pg >= 9.6
|
||||
};
|
||||
authentication = ''
|
||||
host replication ${replicationUser} all trust
|
||||
'';
|
||||
initialScript = pkgs.writeText "init.sql" ''
|
||||
create user ${replicationUser} replication;
|
||||
select * from pg_create_physical_replication_slot('${replicationSlot}');
|
||||
'';
|
||||
};
|
||||
authentication = ''
|
||||
host replication ${replicationUser} all trust
|
||||
'';
|
||||
initialScript = pkgs.writeText "init.sql" ''
|
||||
create user ${replicationUser} replication;
|
||||
select * from pg_create_physical_replication_slot('${replicationSlot}');
|
||||
'';
|
||||
|
||||
services.postgresqlWalReceiver.receivers.main = {
|
||||
postgresqlPackage = package;
|
||||
connection = replicationConn;
|
||||
slot = replicationSlot;
|
||||
directory = walBackupDir;
|
||||
};
|
||||
# This is only to speedup test, it isn't time racing. Service is set to autorestart always,
|
||||
# default 60sec is fine for real system, but is too much for a test
|
||||
systemd.services.postgresql-wal-receiver-main.serviceConfig.RestartSec = lib.mkForce 5;
|
||||
systemd.services.postgresql.serviceConfig.ReadWritePaths = [ "/var/cache/wals" ];
|
||||
};
|
||||
|
||||
services.postgresqlWalReceiver.receivers.main = {
|
||||
postgresqlPackage = package;
|
||||
connection = replicationConn;
|
||||
slot = replicationSlot;
|
||||
directory = walBackupDir;
|
||||
};
|
||||
# This is only to speedup test, it isn't time racing. Service is set to autorestart always,
|
||||
# default 60sec is fine for real system, but is too much for a test
|
||||
systemd.services.postgresql-wal-receiver-main.serviceConfig.RestartSec = lib.mkForce 5;
|
||||
systemd.services.postgresql.serviceConfig.ReadWritePaths = [ "/var/cache/wals" ];
|
||||
};
|
||||
testScript = ''
|
||||
# make an initial base backup
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
machine.wait_for_unit("postgresql-wal-receiver-main")
|
||||
# WAL receiver healthchecks PG every 5 seconds, so let's be sure they have connected each other
|
||||
# required only for 9.4
|
||||
machine.sleep(5)
|
||||
machine.succeed(
|
||||
"${package}/bin/pg_basebackup --dbname=${replicationConn} --pgdata=${baseBackupDir}"
|
||||
)
|
||||
|
||||
testScript = ''
|
||||
# make an initial base backup
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
machine.wait_for_unit("postgresql-wal-receiver-main")
|
||||
# WAL receiver healthchecks PG every 5 seconds, so let's be sure they have connected each other
|
||||
# required only for 9.4
|
||||
machine.sleep(5)
|
||||
machine.succeed(
|
||||
"${package}/bin/pg_basebackup --dbname=${replicationConn} --pgdata=${baseBackupDir}"
|
||||
)
|
||||
# create a dummy table with 100 records
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql --command='create table dummy as select * from generate_series(1, 100) as val;'"
|
||||
)
|
||||
|
||||
# create a dummy table with 100 records
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql --command='create table dummy as select * from generate_series(1, 100) as val;'"
|
||||
)
|
||||
# stop postgres and destroy data
|
||||
machine.systemctl("stop postgresql")
|
||||
machine.systemctl("stop postgresql-wal-receiver-main")
|
||||
machine.succeed("rm -r ${postgresqlDataDir}/{base,global,pg_*}")
|
||||
|
||||
# stop postgres and destroy data
|
||||
machine.systemctl("stop postgresql")
|
||||
machine.systemctl("stop postgresql-wal-receiver-main")
|
||||
machine.succeed("rm -r ${postgresqlDataDir}/{base,global,pg_*}")
|
||||
# restore the base backup
|
||||
machine.succeed(
|
||||
"cp -r ${baseBackupDir}/* ${postgresqlDataDir} && chown postgres:postgres -R ${postgresqlDataDir}"
|
||||
)
|
||||
|
||||
# restore the base backup
|
||||
machine.succeed(
|
||||
"cp -r ${baseBackupDir}/* ${postgresqlDataDir} && chown postgres:postgres -R ${postgresqlDataDir}"
|
||||
)
|
||||
# prepare WAL and recovery
|
||||
machine.succeed("chmod a+rX -R ${walBackupDir}")
|
||||
machine.execute(
|
||||
"for part in ${walBackupDir}/*.partial; do mv $part ''${part%%.*}; done"
|
||||
) # make use of partial segments too
|
||||
machine.succeed(
|
||||
"cp ${recoveryFile}/* ${postgresqlDataDir}/ && chmod 666 ${postgresqlDataDir}/recovery*"
|
||||
)
|
||||
|
||||
# prepare WAL and recovery
|
||||
machine.succeed("chmod a+rX -R ${walBackupDir}")
|
||||
machine.execute(
|
||||
"for part in ${walBackupDir}/*.partial; do mv $part ''${part%%.*}; done"
|
||||
) # make use of partial segments too
|
||||
machine.succeed(
|
||||
"cp ${recoveryFile}/* ${postgresqlDataDir}/ && chmod 666 ${postgresqlDataDir}/recovery*"
|
||||
)
|
||||
# replay WAL
|
||||
machine.systemctl("start postgresql")
|
||||
machine.wait_for_file("${postgresqlDataDir}/recovery.done")
|
||||
machine.systemctl("restart postgresql")
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
|
||||
# replay WAL
|
||||
machine.systemctl("start postgresql")
|
||||
machine.wait_for_file("${postgresqlDataDir}/recovery.done")
|
||||
machine.systemctl("restart postgresql")
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
|
||||
# check that our records have been restored
|
||||
machine.succeed(
|
||||
"test $(sudo -u postgres psql --pset='pager=off' --tuples-only --command='select count(distinct val) from dummy;') -eq 100"
|
||||
)
|
||||
'';
|
||||
};
|
||||
# check that our records have been restored
|
||||
machine.succeed(
|
||||
"test $(sudo -u postgres psql --pset='pager=off' --tuples-only --command='select count(distinct val) from dummy;') -eq 100"
|
||||
)
|
||||
'';
|
||||
}
|
||||
);
|
||||
in
|
||||
genTests { inherit makeTestFor; }
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
{
|
||||
pkgs,
|
||||
makeTest,
|
||||
runTest,
|
||||
genTests,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
|
||||
makeTestFor =
|
||||
package:
|
||||
lib.recurseIntoAttrs {
|
||||
@@ -15,263 +14,271 @@ let
|
||||
postgresql-clauses = makeEnsureTestFor package;
|
||||
};
|
||||
|
||||
test-sql = pkgs.writeText "postgresql-test" ''
|
||||
CREATE EXTENSION pgcrypto; -- just to check if lib loading works
|
||||
CREATE TABLE sth (
|
||||
id int
|
||||
);
|
||||
INSERT INTO sth (id) VALUES (1);
|
||||
INSERT INTO sth (id) VALUES (1);
|
||||
INSERT INTO sth (id) VALUES (1);
|
||||
INSERT INTO sth (id) VALUES (1);
|
||||
INSERT INTO sth (id) VALUES (1);
|
||||
CREATE TABLE xmltest ( doc xml );
|
||||
INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled
|
||||
|
||||
-- check if hardening gets relaxed
|
||||
CREATE EXTENSION plv8;
|
||||
-- try to trigger the V8 JIT, which requires MemoryDenyWriteExecute
|
||||
DO $$
|
||||
let xs = [];
|
||||
for (let i = 0, n = 400000; i < n; i++) {
|
||||
xs.push(Math.round(Math.random() * n))
|
||||
}
|
||||
console.log(xs.reduce((acc, x) => acc + x, 0));
|
||||
$$ LANGUAGE plv8;
|
||||
'';
|
||||
|
||||
makeTestForWithBackupAll =
|
||||
package: backupAll:
|
||||
makeTest {
|
||||
name = "postgresql${lib.optionalString backupAll "-backup-all"}-${package.name}";
|
||||
meta = with lib.maintainers; {
|
||||
maintainers = [ zagy ];
|
||||
};
|
||||
runTest (
|
||||
{ lib, pkgs, ... }:
|
||||
let
|
||||
test-sql = pkgs.writeText "postgresql-test" ''
|
||||
CREATE EXTENSION pgcrypto; -- just to check if lib loading works
|
||||
CREATE TABLE sth (
|
||||
id int
|
||||
);
|
||||
INSERT INTO sth (id) VALUES (1);
|
||||
INSERT INTO sth (id) VALUES (1);
|
||||
INSERT INTO sth (id) VALUES (1);
|
||||
INSERT INTO sth (id) VALUES (1);
|
||||
INSERT INTO sth (id) VALUES (1);
|
||||
CREATE TABLE xmltest ( doc xml );
|
||||
INSERT INTO xmltest (doc) VALUES ('<test>ok</test>'); -- check if libxml2 enabled
|
||||
|
||||
nodes.machine =
|
||||
{ config, ... }:
|
||||
{
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
identMap = ''
|
||||
postgres root postgres
|
||||
'';
|
||||
# TODO(@Ma27) split this off into its own VM test and move a few other
|
||||
# extension tests to use postgresqlTestExtension.
|
||||
extensions = ps: with ps; [ plv8 ];
|
||||
};
|
||||
-- check if hardening gets relaxed
|
||||
CREATE EXTENSION plv8;
|
||||
-- try to trigger the V8 JIT, which requires MemoryDenyWriteExecute
|
||||
DO $$
|
||||
let xs = [];
|
||||
for (let i = 0, n = 400000; i < n; i++) {
|
||||
xs.push(Math.round(Math.random() * n))
|
||||
}
|
||||
console.log(xs.reduce((acc, x) => acc + x, 0));
|
||||
$$ LANGUAGE plv8;
|
||||
'';
|
||||
|
||||
services.postgresqlBackup = {
|
||||
enable = true;
|
||||
databases = lib.optional (!backupAll) "postgres";
|
||||
pgdumpOptions = "--restrict-key=ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
pgdumpAllOptions = "--restrict-key=ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
};
|
||||
in
|
||||
{
|
||||
name = "postgresql${lib.optionalString backupAll "-backup-all"}-${package.name}";
|
||||
meta = with lib.maintainers; {
|
||||
maintainers = [ zagy ];
|
||||
};
|
||||
|
||||
testScript =
|
||||
let
|
||||
backupName = if backupAll then "all" else "postgres";
|
||||
backupService = if backupAll then "postgresqlBackup" else "postgresqlBackup-postgres";
|
||||
backupFileBase = "/var/backup/postgresql/${backupName}";
|
||||
in
|
||||
''
|
||||
def check_count(statement, lines):
|
||||
return 'test $(psql -U postgres postgres -tAc "{}"|wc -l) -eq {}'.format(
|
||||
statement, lines
|
||||
)
|
||||
nodes.machine =
|
||||
{ config, ... }:
|
||||
{
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
identMap = ''
|
||||
postgres root postgres
|
||||
'';
|
||||
# TODO(@Ma27) split this off into its own VM test and move a few other
|
||||
# extension tests to use postgresqlTestExtension.
|
||||
extensions = ps: with ps; [ plv8 ];
|
||||
};
|
||||
|
||||
services.postgresqlBackup = {
|
||||
enable = true;
|
||||
databases = lib.optional (!backupAll) "postgres";
|
||||
pgdumpOptions = "--restrict-key=ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
pgdumpAllOptions = "--restrict-key=ABCDEFGHIJKLMNOPQRSTUVWXYZ";
|
||||
};
|
||||
};
|
||||
|
||||
testScript =
|
||||
let
|
||||
backupName = if backupAll then "all" else "postgres";
|
||||
backupService = if backupAll then "postgresqlBackup" else "postgresqlBackup-postgres";
|
||||
backupFileBase = "/var/backup/postgresql/${backupName}";
|
||||
in
|
||||
''
|
||||
def check_count(statement, lines):
|
||||
return 'test $(psql -U postgres postgres -tAc "{}"|wc -l) -eq {}'.format(
|
||||
statement, lines
|
||||
)
|
||||
|
||||
|
||||
machine.start()
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
machine.start()
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
|
||||
with subtest("Postgresql is available just after unit start"):
|
||||
machine.succeed(
|
||||
"cat ${test-sql} | sudo -u postgres psql"
|
||||
)
|
||||
with subtest("Postgresql is available just after unit start"):
|
||||
machine.succeed(
|
||||
"cat ${test-sql} | sudo -u postgres psql"
|
||||
)
|
||||
|
||||
with subtest("Postgresql survives restart (bug #1735)"):
|
||||
machine.shutdown()
|
||||
import time
|
||||
time.sleep(2)
|
||||
machine.start()
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
with subtest("Postgresql survives restart (bug #1735)"):
|
||||
machine.shutdown()
|
||||
import time
|
||||
time.sleep(2)
|
||||
machine.start()
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
|
||||
machine.fail(check_count("SELECT * FROM sth;", 3))
|
||||
machine.succeed(check_count("SELECT * FROM sth;", 5))
|
||||
machine.fail(check_count("SELECT * FROM sth;", 4))
|
||||
machine.succeed(check_count("SELECT xpath('/test/text()', doc) FROM xmltest;", 1))
|
||||
machine.fail(check_count("SELECT * FROM sth;", 3))
|
||||
machine.succeed(check_count("SELECT * FROM sth;", 5))
|
||||
machine.fail(check_count("SELECT * FROM sth;", 4))
|
||||
machine.succeed(check_count("SELECT xpath('/test/text()', doc) FROM xmltest;", 1))
|
||||
|
||||
with subtest("killing postgres process should trigger an automatic restart"):
|
||||
machine.succeed("systemctl kill -s KILL postgresql")
|
||||
with subtest("killing postgres process should trigger an automatic restart"):
|
||||
machine.succeed("systemctl kill -s KILL postgresql")
|
||||
|
||||
machine.wait_until_succeeds("systemctl is-active postgresql.service")
|
||||
machine.wait_until_succeeds("systemctl is-active postgresql.target")
|
||||
machine.wait_until_succeeds("systemctl is-active postgresql.service")
|
||||
machine.wait_until_succeeds("systemctl is-active postgresql.target")
|
||||
|
||||
with subtest("Backup service works"):
|
||||
machine.succeed(
|
||||
"systemctl start ${backupService}.service",
|
||||
"zcat ${backupFileBase}.sql.gz | grep '<test>ok</test>'",
|
||||
"ls -hal /var/backup/postgresql/ >/dev/console",
|
||||
"stat -c '%a' ${backupFileBase}.sql.gz | grep 600",
|
||||
)
|
||||
with subtest("Backup service removes prev files"):
|
||||
machine.succeed(
|
||||
# Create dummy prev files.
|
||||
"touch ${backupFileBase}.prev.sql{,.gz,.zstd}",
|
||||
"chown postgres:postgres ${backupFileBase}.prev.sql{,.gz,.zstd}",
|
||||
with subtest("Backup service works"):
|
||||
machine.succeed(
|
||||
"systemctl start ${backupService}.service",
|
||||
"zcat ${backupFileBase}.sql.gz | grep '<test>ok</test>'",
|
||||
"ls -hal /var/backup/postgresql/ >/dev/console",
|
||||
"stat -c '%a' ${backupFileBase}.sql.gz | grep 600",
|
||||
)
|
||||
with subtest("Backup service removes prev files"):
|
||||
machine.succeed(
|
||||
# Create dummy prev files.
|
||||
"touch ${backupFileBase}.prev.sql{,.gz,.zstd}",
|
||||
"chown postgres:postgres ${backupFileBase}.prev.sql{,.gz,.zstd}",
|
||||
|
||||
# Run backup.
|
||||
"systemctl start ${backupService}.service",
|
||||
"ls -hal /var/backup/postgresql/ >/dev/console",
|
||||
# Run backup.
|
||||
"systemctl start ${backupService}.service",
|
||||
"ls -hal /var/backup/postgresql/ >/dev/console",
|
||||
|
||||
# Since nothing has changed in the database, the cur and prev files
|
||||
# should match.
|
||||
"zcat ${backupFileBase}.sql.gz | grep '<test>ok</test>'",
|
||||
"cmp ${backupFileBase}.sql.gz ${backupFileBase}.prev.sql.gz",
|
||||
# Since nothing has changed in the database, the cur and prev files
|
||||
# should match.
|
||||
"zcat ${backupFileBase}.sql.gz | grep '<test>ok</test>'",
|
||||
"cmp ${backupFileBase}.sql.gz ${backupFileBase}.prev.sql.gz",
|
||||
|
||||
# The prev files with unused suffix should be removed.
|
||||
"[ ! -f '${backupFileBase}.prev.sql' ]",
|
||||
"[ ! -f '${backupFileBase}.prev.sql.zstd' ]",
|
||||
# The prev files with unused suffix should be removed.
|
||||
"[ ! -f '${backupFileBase}.prev.sql' ]",
|
||||
"[ ! -f '${backupFileBase}.prev.sql.zstd' ]",
|
||||
|
||||
# Both cur and prev file should only be accessible by the postgres user.
|
||||
"stat -c '%a' ${backupFileBase}.sql.gz | grep 600",
|
||||
"stat -c '%a' '${backupFileBase}.prev.sql.gz' | grep 600",
|
||||
)
|
||||
with subtest("Backup service fails gracefully"):
|
||||
# Sabotage the backup process
|
||||
machine.succeed("rm /run/postgresql/.s.PGSQL.5432")
|
||||
machine.fail(
|
||||
"systemctl start ${backupService}.service",
|
||||
)
|
||||
machine.succeed(
|
||||
"ls -hal /var/backup/postgresql/ >/dev/console",
|
||||
"zcat ${backupFileBase}.prev.sql.gz | grep '<test>ok</test>'",
|
||||
"stat ${backupFileBase}.in-progress.sql.gz",
|
||||
)
|
||||
# In a previous version, the second run would overwrite prev.sql.gz,
|
||||
# so we test a second run as well.
|
||||
machine.fail(
|
||||
"systemctl start ${backupService}.service",
|
||||
)
|
||||
machine.succeed(
|
||||
"stat ${backupFileBase}.in-progress.sql.gz",
|
||||
"zcat ${backupFileBase}.prev.sql.gz | grep '<test>ok</test>'",
|
||||
)
|
||||
# Both cur and prev file should only be accessible by the postgres user.
|
||||
"stat -c '%a' ${backupFileBase}.sql.gz | grep 600",
|
||||
"stat -c '%a' '${backupFileBase}.prev.sql.gz' | grep 600",
|
||||
)
|
||||
with subtest("Backup service fails gracefully"):
|
||||
# Sabotage the backup process
|
||||
machine.succeed("rm /run/postgresql/.s.PGSQL.5432")
|
||||
machine.fail(
|
||||
"systemctl start ${backupService}.service",
|
||||
)
|
||||
machine.succeed(
|
||||
"ls -hal /var/backup/postgresql/ >/dev/console",
|
||||
"zcat ${backupFileBase}.prev.sql.gz | grep '<test>ok</test>'",
|
||||
"stat ${backupFileBase}.in-progress.sql.gz",
|
||||
)
|
||||
# In a previous version, the second run would overwrite prev.sql.gz,
|
||||
# so we test a second run as well.
|
||||
machine.fail(
|
||||
"systemctl start ${backupService}.service",
|
||||
)
|
||||
machine.succeed(
|
||||
"stat ${backupFileBase}.in-progress.sql.gz",
|
||||
"zcat ${backupFileBase}.prev.sql.gz | grep '<test>ok</test>'",
|
||||
)
|
||||
|
||||
|
||||
with subtest("Initdb works"):
|
||||
machine.succeed("sudo -u postgres initdb -D /tmp/testpostgres2")
|
||||
with subtest("Initdb works"):
|
||||
machine.succeed("sudo -u postgres initdb -D /tmp/testpostgres2")
|
||||
|
||||
machine.log(machine.execute("systemd-analyze security postgresql.service | grep -v ✓")[1])
|
||||
machine.log(machine.execute("systemd-analyze security postgresql.service | grep -v ✓")[1])
|
||||
|
||||
machine.shutdown()
|
||||
'';
|
||||
};
|
||||
machine.shutdown()
|
||||
'';
|
||||
}
|
||||
);
|
||||
|
||||
makeEnsureTestFor =
|
||||
package:
|
||||
makeTest {
|
||||
name = "postgresql-clauses-${package.name}";
|
||||
meta = with lib.maintainers; {
|
||||
maintainers = [ zagy ];
|
||||
};
|
||||
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "all-clauses";
|
||||
ensureClauses = {
|
||||
superuser = true;
|
||||
createdb = true;
|
||||
createrole = true;
|
||||
"inherit" = true;
|
||||
login = true;
|
||||
replication = true;
|
||||
bypassrls = true;
|
||||
# SCRAM-SHA-256 hashed password for "password"
|
||||
password = "SCRAM-SHA-256$4096:SZEJF5Si4QZ6l4fedrZZWQ==$6u3PWVcz+dts+NdpByPIjKa4CaSnoXGG3M2vpo76bVU=:WSZ0iGUCmVtKYVvNX0pFOp/60IgsdJ+90Y67Eun+QE0=";
|
||||
connection_limit = 5;
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "default-clauses";
|
||||
}
|
||||
];
|
||||
};
|
||||
runTest (
|
||||
{ lib, ... }:
|
||||
{
|
||||
name = "postgresql-clauses-${package.name}";
|
||||
meta = with lib.maintainers; {
|
||||
maintainers = [ zagy ];
|
||||
};
|
||||
|
||||
testScript =
|
||||
let
|
||||
getClausesQuery =
|
||||
user:
|
||||
lib.concatStringsSep " " [
|
||||
"SELECT row_to_json(row)"
|
||||
"FROM ("
|
||||
"SELECT"
|
||||
"rolsuper,"
|
||||
"rolinherit,"
|
||||
"rolcreaterole,"
|
||||
"rolcreatedb,"
|
||||
"rolcanlogin,"
|
||||
"rolreplication,"
|
||||
"rolbypassrls,"
|
||||
"rolconnlimit,"
|
||||
"rolpassword"
|
||||
"FROM pg_authid"
|
||||
"WHERE rolname = '${user}'"
|
||||
") row;"
|
||||
];
|
||||
in
|
||||
''
|
||||
import json
|
||||
machine.start()
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
nodes.machine =
|
||||
{ ... }:
|
||||
{
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
ensureUsers = [
|
||||
{
|
||||
name = "all-clauses";
|
||||
ensureClauses = {
|
||||
superuser = true;
|
||||
createdb = true;
|
||||
createrole = true;
|
||||
"inherit" = true;
|
||||
login = true;
|
||||
replication = true;
|
||||
bypassrls = true;
|
||||
# SCRAM-SHA-256 hashed password for "password"
|
||||
password = "SCRAM-SHA-256$4096:SZEJF5Si4QZ6l4fedrZZWQ==$6u3PWVcz+dts+NdpByPIjKa4CaSnoXGG3M2vpo76bVU=:WSZ0iGUCmVtKYVvNX0pFOp/60IgsdJ+90Y67Eun+QE0=";
|
||||
connection_limit = 5;
|
||||
};
|
||||
}
|
||||
{
|
||||
name = "default-clauses";
|
||||
}
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
with subtest("All user permissions are set according to the ensureClauses attr"):
|
||||
clauses = json.loads(
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql -tc \"${getClausesQuery "all-clauses"}\""
|
||||
testScript =
|
||||
let
|
||||
getClausesQuery =
|
||||
user:
|
||||
lib.concatStringsSep " " [
|
||||
"SELECT row_to_json(row)"
|
||||
"FROM ("
|
||||
"SELECT"
|
||||
"rolsuper,"
|
||||
"rolinherit,"
|
||||
"rolcreaterole,"
|
||||
"rolcreatedb,"
|
||||
"rolcanlogin,"
|
||||
"rolreplication,"
|
||||
"rolbypassrls,"
|
||||
"rolconnlimit,"
|
||||
"rolpassword"
|
||||
"FROM pg_authid"
|
||||
"WHERE rolname = '${user}'"
|
||||
") row;"
|
||||
];
|
||||
in
|
||||
''
|
||||
import json
|
||||
machine.start()
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
|
||||
with subtest("All user permissions are set according to the ensureClauses attr"):
|
||||
clauses = json.loads(
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql -tc \"${getClausesQuery "all-clauses"}\""
|
||||
)
|
||||
)
|
||||
)
|
||||
print(clauses)
|
||||
t.assertTrue(clauses["rolsuper"])
|
||||
t.assertTrue(clauses["rolinherit"])
|
||||
t.assertTrue(clauses["rolcreaterole"])
|
||||
t.assertTrue(clauses["rolcreatedb"])
|
||||
t.assertTrue(clauses["rolcanlogin"])
|
||||
t.assertTrue(clauses["rolreplication"])
|
||||
t.assertTrue(clauses["rolbypassrls"])
|
||||
t.assertTrue(clauses["rolconnlimit"] == 5)
|
||||
t.assertTrue(clauses["rolpassword"])
|
||||
machine.succeed(
|
||||
"PGPASSWORD='password' psql -h localhost -U all-clauses -d postgres -c \"SELECT 1\""
|
||||
)
|
||||
|
||||
with subtest("All user permissions default when ensureClauses is not provided"):
|
||||
clauses = json.loads(
|
||||
print(clauses)
|
||||
t.assertTrue(clauses["rolsuper"])
|
||||
t.assertTrue(clauses["rolinherit"])
|
||||
t.assertTrue(clauses["rolcreaterole"])
|
||||
t.assertTrue(clauses["rolcreatedb"])
|
||||
t.assertTrue(clauses["rolcanlogin"])
|
||||
t.assertTrue(clauses["rolreplication"])
|
||||
t.assertTrue(clauses["rolbypassrls"])
|
||||
t.assertTrue(clauses["rolconnlimit"] == 5)
|
||||
t.assertTrue(clauses["rolpassword"])
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql -tc \"${getClausesQuery "default-clauses"}\""
|
||||
"PGPASSWORD='password' psql -h localhost -U all-clauses -d postgres -c \"SELECT 1\""
|
||||
)
|
||||
)
|
||||
t.assertFalse(clauses["rolsuper"])
|
||||
t.assertTrue(clauses["rolinherit"])
|
||||
t.assertFalse(clauses["rolcreaterole"])
|
||||
t.assertFalse(clauses["rolcreatedb"])
|
||||
t.assertTrue(clauses["rolcanlogin"])
|
||||
t.assertFalse(clauses["rolreplication"])
|
||||
t.assertFalse(clauses["rolbypassrls"])
|
||||
t.assertFalse(clauses["rolconnlimit"] == 5)
|
||||
t.assertFalse(clauses["rolpassword"])
|
||||
|
||||
machine.shutdown()
|
||||
'';
|
||||
};
|
||||
with subtest("All user permissions default when ensureClauses is not provided"):
|
||||
clauses = json.loads(
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql -tc \"${getClausesQuery "default-clauses"}\""
|
||||
)
|
||||
)
|
||||
t.assertFalse(clauses["rolsuper"])
|
||||
t.assertTrue(clauses["rolinherit"])
|
||||
t.assertFalse(clauses["rolcreaterole"])
|
||||
t.assertFalse(clauses["rolcreatedb"])
|
||||
t.assertTrue(clauses["rolcanlogin"])
|
||||
t.assertFalse(clauses["rolreplication"])
|
||||
t.assertFalse(clauses["rolbypassrls"])
|
||||
t.assertFalse(clauses["rolconnlimit"] == 5)
|
||||
t.assertFalse(clauses["rolpassword"])
|
||||
|
||||
machine.shutdown()
|
||||
'';
|
||||
}
|
||||
);
|
||||
in
|
||||
genTests { inherit makeTestFor; }
|
||||
|
||||
@@ -1,47 +1,48 @@
|
||||
{
|
||||
pkgs,
|
||||
makeTest,
|
||||
runTest,
|
||||
genTests,
|
||||
...
|
||||
}:
|
||||
|
||||
let
|
||||
inherit (pkgs) lib;
|
||||
|
||||
makeTestFor =
|
||||
package:
|
||||
makeTest {
|
||||
name = "wal2json-${package.name}";
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ euank ];
|
||||
runTest (
|
||||
{ lib, pkgs, ... }:
|
||||
{
|
||||
name = "wal2json-${package.name}";
|
||||
meta.maintainers = with pkgs.lib.maintainers; [ euank ];
|
||||
|
||||
nodes.machine = {
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
extensions = with package.pkgs; [ wal2json ];
|
||||
settings = {
|
||||
wal_level = "logical";
|
||||
max_replication_slots = "10";
|
||||
max_wal_senders = "10";
|
||||
nodes.machine = {
|
||||
services.postgresql = {
|
||||
inherit package;
|
||||
enable = true;
|
||||
extensions = with package.pkgs; [ wal2json ];
|
||||
settings = {
|
||||
wal_level = "logical";
|
||||
max_replication_slots = "10";
|
||||
max_wal_senders = "10";
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
testScript = ''
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql -qAt -f ${./wal2json/example2.sql} postgres > /tmp/example2.out"
|
||||
)
|
||||
machine.succeed(
|
||||
"diff ${./wal2json/example2.out} /tmp/example2.out"
|
||||
)
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql -qAt -f ${./wal2json/example3.sql} postgres > /tmp/example3.out"
|
||||
)
|
||||
machine.succeed(
|
||||
"diff ${./wal2json/example3.out} /tmp/example3.out"
|
||||
)
|
||||
'';
|
||||
};
|
||||
testScript = ''
|
||||
machine.wait_for_unit("postgresql.target")
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql -qAt -f ${./wal2json/example2.sql} postgres > /tmp/example2.out"
|
||||
)
|
||||
machine.succeed(
|
||||
"diff ${./wal2json/example2.out} /tmp/example2.out"
|
||||
)
|
||||
machine.succeed(
|
||||
"sudo -u postgres psql -qAt -f ${./wal2json/example3.sql} postgres > /tmp/example3.out"
|
||||
)
|
||||
machine.succeed(
|
||||
"diff ${./wal2json/example3.out} /tmp/example3.out"
|
||||
)
|
||||
'';
|
||||
}
|
||||
);
|
||||
in
|
||||
genTests {
|
||||
inherit makeTestFor;
|
||||
|
||||
Reference in New Issue
Block a user