test: sanity tests for primary and replica
* new --replica option to `postgrest-with-postgresql-*` * new command `postgrest-test-replica` * new sanity tests on test_replica.py * add postgrest-test-replica to postgrest-check and postgrest-coverage
This commit is contained in:
committed by
Steve Chavez
parent
cdb877135b
commit
d9a51f23ce
@@ -77,6 +77,7 @@ let
|
|||||||
${tests}/bin/postgrest-test-doctests
|
${tests}/bin/postgrest-test-doctests
|
||||||
${tests}/bin/postgrest-test-io
|
${tests}/bin/postgrest-test-io
|
||||||
${tests}/bin/postgrest-test-big-schema
|
${tests}/bin/postgrest-test-big-schema
|
||||||
|
${tests}/bin/postgrest-test-replica
|
||||||
${style}/bin/postgrest-lint
|
${style}/bin/postgrest-lint
|
||||||
${style}/bin/postgrest-style-check
|
${style}/bin/postgrest-style-check
|
||||||
'';
|
'';
|
||||||
|
|||||||
+23
-3
@@ -84,7 +84,7 @@ let
|
|||||||
''
|
''
|
||||||
${cabal-install}/bin/cabal v2-build ${devCabalOptions}
|
${cabal-install}/bin/cabal v2-build ${devCabalOptions}
|
||||||
${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} -f test/io/fixtures.sql \
|
${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} -f test/io/fixtures.sql \
|
||||||
${ioTestPython}/bin/pytest --ignore=test/io/test_big_schema.py -v test/io "''${_arg_leftovers[@]}"
|
${ioTestPython}/bin/pytest --ignore=test/io/test_big_schema.py --ignore=test/io/test_replica.py -v test/io "''${_arg_leftovers[@]}"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
testBigSchema =
|
testBigSchema =
|
||||||
@@ -102,6 +102,21 @@ let
|
|||||||
${ioTestPython}/bin/pytest -v test/io/test_big_schema.py "''${_arg_leftovers[@]}"
|
${ioTestPython}/bin/pytest -v test/io/test_big_schema.py "''${_arg_leftovers[@]}"
|
||||||
'';
|
'';
|
||||||
|
|
||||||
|
testReplica =
|
||||||
|
checkedShellScript
|
||||||
|
{
|
||||||
|
name = "postgrest-test-replica";
|
||||||
|
docs = "Run a pytest-based IO test on a replica. Add -k to run tests that match a given expression.";
|
||||||
|
args = [ "ARG_LEFTOVERS([pytest arguments])" ];
|
||||||
|
workingDir = "/";
|
||||||
|
withEnv = postgrest.env;
|
||||||
|
}
|
||||||
|
''
|
||||||
|
${cabal-install}/bin/cabal v2-build ${devCabalOptions}
|
||||||
|
${cabal-install}/bin/cabal v2-exec -- ${withTools.withPg} --replica -f test/io/replica.sql \
|
||||||
|
${ioTestPython}/bin/pytest -v test/io/test_replica.py "''${_arg_leftovers[@]}"
|
||||||
|
'';
|
||||||
|
|
||||||
dumpSchema =
|
dumpSchema =
|
||||||
checkedShellScript
|
checkedShellScript
|
||||||
{
|
{
|
||||||
@@ -150,12 +165,16 @@ let
|
|||||||
# collect all tests
|
# collect all tests
|
||||||
HPCTIXFILE="$tmpdir"/io.tix \
|
HPCTIXFILE="$tmpdir"/io.tix \
|
||||||
${withTools.withPg} -f test/io/fixtures.sql \
|
${withTools.withPg} -f test/io/fixtures.sql \
|
||||||
${cabal-install}/bin/cabal v2-exec ${devCabalOptions} -- ${ioTestPython}/bin/pytest --ignore=test/io/test_big_schema.py -v test/io
|
${cabal-install}/bin/cabal v2-exec ${devCabalOptions} -- ${ioTestPython}/bin/pytest --ignore=test/io/test_big_schema.py --ignore=test/io/test_replica.py -v test/io
|
||||||
|
|
||||||
HPCTIXFILE="$tmpdir"/big_schema.tix \
|
HPCTIXFILE="$tmpdir"/big_schema.tix \
|
||||||
${withTools.withPg} -f test/io/big_schema.sql \
|
${withTools.withPg} -f test/io/big_schema.sql \
|
||||||
${cabal-install}/bin/cabal v2-exec ${devCabalOptions} -- ${ioTestPython}/bin/pytest -v test/io/test_big_schema.py
|
${cabal-install}/bin/cabal v2-exec ${devCabalOptions} -- ${ioTestPython}/bin/pytest -v test/io/test_big_schema.py
|
||||||
|
|
||||||
|
HPCTIXFILE="$tmpdir"/replica.tix \
|
||||||
|
${withTools.withPg} --replica -f test/io/replica.sql \
|
||||||
|
${cabal-install}/bin/cabal v2-exec ${devCabalOptions} -- ${ioTestPython}/bin/pytest -v test/io/test_replica.py
|
||||||
|
|
||||||
HPCTIXFILE="$tmpdir"/spec.tix \
|
HPCTIXFILE="$tmpdir"/spec.tix \
|
||||||
${withTools.withPg} -f test/spec/fixtures/load.sql \
|
${withTools.withPg} -f test/spec/fixtures/load.sql \
|
||||||
${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec
|
${cabal-install}/bin/cabal v2-run ${devCabalOptions} test:spec
|
||||||
@@ -164,7 +183,7 @@ let
|
|||||||
|
|
||||||
# collect all the tix files
|
# collect all the tix files
|
||||||
${ghc}/bin/hpc sum --union --exclude=Paths_postgrest --output="$tmpdir"/tests.tix \
|
${ghc}/bin/hpc sum --union --exclude=Paths_postgrest --output="$tmpdir"/tests.tix \
|
||||||
"$tmpdir"/io*.tix "$tmpdir"/big_schema*.tix "$tmpdir"/spec.tix
|
"$tmpdir"/io*.tix "$tmpdir"/big_schema*.tix "$tmpdir"/replica*.tix "$tmpdir"/spec.tix
|
||||||
|
|
||||||
# prepare the overlay
|
# prepare the overlay
|
||||||
${ghc}/bin/hpc overlay --output="$tmpdir"/overlay.tix test/coverage.overlay
|
${ghc}/bin/hpc overlay --output="$tmpdir"/overlay.tix test/coverage.overlay
|
||||||
@@ -220,6 +239,7 @@ buildToolbox
|
|||||||
testSpecIdempotence
|
testSpecIdempotence
|
||||||
testIO
|
testIO
|
||||||
testBigSchema
|
testBigSchema
|
||||||
|
testReplica
|
||||||
dumpSchema
|
dumpSchema
|
||||||
coverage
|
coverage
|
||||||
coverageDraftOverlay;
|
coverageDraftOverlay;
|
||||||
|
|||||||
+42
-9
@@ -28,6 +28,7 @@ let
|
|||||||
"ARG_USE_ENV([PGRST_DB_SCHEMAS], [test], [Schema to expose])"
|
"ARG_USE_ENV([PGRST_DB_SCHEMAS], [test], [Schema to expose])"
|
||||||
"ARG_USE_ENV([PGTZ], [utc], [Timezone to use])"
|
"ARG_USE_ENV([PGTZ], [utc], [Timezone to use])"
|
||||||
"ARG_USE_ENV([PGOPTIONS], [-c search_path=public,test], [PG options to use])"
|
"ARG_USE_ENV([PGOPTIONS], [-c search_path=public,test], [PG options to use])"
|
||||||
|
"ARG_OPTIONAL_BOOLEAN([replica],, [Enable a replica for the database])"
|
||||||
];
|
];
|
||||||
positionalCompletion = "_command";
|
positionalCompletion = "_command";
|
||||||
workingDir = "/";
|
workingDir = "/";
|
||||||
@@ -61,6 +62,7 @@ let
|
|||||||
HBA_FILE="$tmpdir/pg_hba.conf"
|
HBA_FILE="$tmpdir/pg_hba.conf"
|
||||||
echo "local $PGDATABASE some_protected_user password" > "$HBA_FILE"
|
echo "local $PGDATABASE some_protected_user password" > "$HBA_FILE"
|
||||||
echo "local $PGDATABASE all trust" >> "$HBA_FILE"
|
echo "local $PGDATABASE all trust" >> "$HBA_FILE"
|
||||||
|
echo "local replication all trust" >> "$HBA_FILE"
|
||||||
|
|
||||||
log "Initializing database cluster..."
|
log "Initializing database cluster..."
|
||||||
# We try to make the database cluster as independent as possible from the host
|
# We try to make the database cluster as independent as possible from the host
|
||||||
@@ -74,19 +76,50 @@ let
|
|||||||
pg_ctl -l "$tmpdir/db.log" -w start -o "-F -c listen_addresses=\"\" -c hba_file=$HBA_FILE -k $PGHOST -c log_statement=\"all\" " \
|
pg_ctl -l "$tmpdir/db.log" -w start -o "-F -c listen_addresses=\"\" -c hba_file=$HBA_FILE -k $PGHOST -c log_statement=\"all\" " \
|
||||||
>> "$setuplog"
|
>> "$setuplog"
|
||||||
|
|
||||||
# shellcheck disable=SC2317
|
|
||||||
stop () {
|
|
||||||
log "Stopping the database cluster..."
|
|
||||||
pg_ctl stop -m i >> "$setuplog"
|
|
||||||
rm -rf "$tmpdir/db"
|
|
||||||
}
|
|
||||||
trap stop EXIT
|
|
||||||
|
|
||||||
log "Creating a minimally privileged $PGUSER connection role..."
|
log "Creating a minimally privileged $PGUSER connection role..."
|
||||||
createuser "$PGUSER" -U postgres --host="$tmpdir/socket" --no-createdb --no-inherit --no-superuser --no-createrole --no-replication --login
|
createuser "$PGUSER" -U postgres --host="$tmpdir/socket" --no-createdb --no-inherit --no-superuser --no-createrole --no-replication --login
|
||||||
|
|
||||||
>&2 echo "${commandName}: You can connect with: psql 'postgres:///$PGDATABASE?host=$tmpdir/socket' -U postgres"
|
>&2 echo "${commandName}: You can connect with: psql 'postgres:///$PGDATABASE?host=$PGHOST' -U postgres"
|
||||||
>&2 echo "${commandName}: You can tail the logs with: tail -f $tmpdir/db.log"
|
>&2 echo "${commandName}: You can tail the logs with: tail -f $tmpdir/db.log"
|
||||||
|
|
||||||
|
if test "$_arg_replica" = "on"; then
|
||||||
|
replica_slot="replica_$RANDOM"
|
||||||
|
replica_dir="$tmpdir/$replica_slot"
|
||||||
|
replica_host="$tmpdir/socket_$replica_slot"
|
||||||
|
|
||||||
|
mkdir -p "$replica_host"
|
||||||
|
|
||||||
|
replica_dblog="$tmpdir/db_$replica_slot.log"
|
||||||
|
|
||||||
|
log "Running pg_basebackup for $replica_slot"
|
||||||
|
|
||||||
|
pg_basebackup -v -h "$PGHOST" -U postgres --wal-method=stream --create-slot --slot="$replica_slot" --write-recovery-conf -D "$replica_dir" \
|
||||||
|
>> "$setuplog" 2>&1
|
||||||
|
|
||||||
|
log "Starting replica on $replica_host"
|
||||||
|
|
||||||
|
pg_ctl -D "$replica_dir" -l "$replica_dblog" -w start -o "-F -c listen_addresses=\"\" -c hba_file=$HBA_FILE -k $replica_host -c log_statement=\"all\" " \
|
||||||
|
>> "$setuplog"
|
||||||
|
|
||||||
|
>&2 echo "${commandName}: Replica enabled. You can connect to it with: psql 'postgres:///$PGDATABASE?host=$replica_host' -U postgres"
|
||||||
|
>&2 echo "${commandName}: You can tail the replica logs with: tail -f $replica_dblog"
|
||||||
|
|
||||||
|
export PGREPLICAHOST="$replica_host"
|
||||||
|
export PGREPLICASLOT="$replica_slot"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# shellcheck disable=SC2317
|
||||||
|
stop () {
|
||||||
|
log "Stopping the database cluster..."
|
||||||
|
pg_ctl stop --mode=immediate >> "$setuplog"
|
||||||
|
rm -rf "$tmpdir/db"
|
||||||
|
if test "$_arg_replica" = "on"; then
|
||||||
|
log "Stopping the replica cluster..."
|
||||||
|
pg_ctl -D "$replica_dir" stop --mode=immediate >> "$setuplog"
|
||||||
|
rm -rf "$replica_dir"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
trap stop EXIT
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if test "$_arg_fixtures"; then
|
if test "$_arg_fixtures"; then
|
||||||
|
|||||||
@@ -45,6 +45,27 @@ def defaultenv(baseenv):
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def replicaenv(defaultenv):
|
||||||
|
"Default environment for a PostgREST replica."
|
||||||
|
conf = {
|
||||||
|
"PGRST_DB_ANON_ROLE": "postgrest_test_anonymous",
|
||||||
|
"PGRST_DB_SCHEMAS": "replica",
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
"primary": {
|
||||||
|
**defaultenv,
|
||||||
|
**conf,
|
||||||
|
},
|
||||||
|
"replica": {
|
||||||
|
**defaultenv,
|
||||||
|
**conf,
|
||||||
|
"PGHOST": os.environ["PGREPLICAHOST"],
|
||||||
|
"PGREPLICASLOT": os.environ["PGREPLICASLOT"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture
|
@pytest.fixture
|
||||||
def slow_schema_cache_env(defaultenv):
|
def slow_schema_cache_env(defaultenv):
|
||||||
"Slow schema cache load environment PostgREST."
|
"Slow schema cache load environment PostgREST."
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
create schema replica;
|
||||||
|
|
||||||
|
create or replace function replica.is_replica() returns bool as $$
|
||||||
|
select pg_is_in_recovery();
|
||||||
|
$$ language sql;
|
||||||
|
|
||||||
|
create or replace function replica.get_replica_slot() returns name as $$
|
||||||
|
select slot_name from pg_replication_slots limit 1;
|
||||||
|
$$ language sql;
|
||||||
|
|
||||||
|
create table replica.items as select x as id from generate_series(1, 10) x;
|
||||||
|
|
||||||
|
DROP ROLE IF EXISTS postgrest_test_anonymous;
|
||||||
|
CREATE ROLE postgrest_test_anonymous;
|
||||||
|
|
||||||
|
GRANT postgrest_test_anonymous TO :PGUSER;
|
||||||
|
|
||||||
|
GRANT USAGE ON SCHEMA replica TO postgrest_test_anonymous;
|
||||||
|
|
||||||
|
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA replica
|
||||||
|
TO postgrest_test_anonymous;
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
"IO tests for PostgREST started on replicas"
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from config import *
|
||||||
|
from util import *
|
||||||
|
from postgrest import *
|
||||||
|
|
||||||
|
|
||||||
|
def test_sanity_replica(replicaenv):
|
||||||
|
"Test that primary and replica are working as intended"
|
||||||
|
|
||||||
|
with run(env=replicaenv["primary"]) as postgrest:
|
||||||
|
response = postgrest.session.get("/rpc/is_replica")
|
||||||
|
assert response.text == "false"
|
||||||
|
|
||||||
|
response = postgrest.session.get("/rpc/get_replica_slot")
|
||||||
|
assert response.text == '"' + replicaenv["replica"]["PGREPLICASLOT"] + '"'
|
||||||
|
|
||||||
|
response = postgrest.session.get("/items?select=count")
|
||||||
|
assert response.text == '[{"count":10}]'
|
||||||
|
|
||||||
|
with run(env=replicaenv["replica"]) as postgrest:
|
||||||
|
response = postgrest.session.get("/rpc/is_replica")
|
||||||
|
assert response.text == "true"
|
||||||
|
|
||||||
|
response = postgrest.session.get("/items?select=count")
|
||||||
|
assert response.text == '[{"count":10}]'
|
||||||
Reference in New Issue
Block a user