Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ let
allOverlays.checked-shell-script
allOverlays.gitignore
(allOverlays.haskell-packages { inherit compiler; })
allOverlays.slocat
];

# Evaluated expression of the Nixpkgs repository.
Expand Down
1 change: 0 additions & 1 deletion nix/overlays/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,4 @@
checked-shell-script = import ./checked-shell-script;
gitignore = import ./gitignore.nix;
haskell-packages = import ./haskell-packages.nix;
slocat = import ./slocat.nix;
}
13 changes: 0 additions & 13 deletions nix/overlays/slocat.nix

This file was deleted.

196 changes: 159 additions & 37 deletions nix/tools/withTools.nix
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@
, postgresqlVersions
, postgrest
, python3Packages
, slocat
, socat
, writeText
, writers
, toxiproxy
}:
let
withTmpDb =
Expand Down Expand Up @@ -55,6 +56,8 @@ let

export PGDATA="$tmpdir/db"
export PGHOST="$tmpdir/socket"
PGPORT=$(${randomPort})
export PGPORT
export PGUSER
export PGDATABASE
export PGRST_DB_SCHEMAS
Expand All @@ -63,8 +66,15 @@ let

HBA_FILE="$tmpdir/pg_hba.conf"
echo "local $PGDATABASE some_protected_user password" > "$HBA_FILE"
echo "local $PGDATABASE all trust" >> "$HBA_FILE"
echo "local replication all trust" >> "$HBA_FILE"
{
echo "local $PGDATABASE all trust"
echo "local replication all trust"
echo "host $PGDATABASE some_protected_user localhost scram-sha-256"
echo "host $PGDATABASE all localhost trust"
} >> "$HBA_FILE"

UNIX_PGHOST="$PGHOST"
export TCP_PGHOST="localhost"

log "Initializing database cluster..."
# We try to make the database cluster as independent as possible from the host
Expand All @@ -81,7 +91,7 @@ let
# On MacOS, it's 104 chars
# See: https://serverfault.com/questions/641347/check-if-a-path-exceeds-maximum-for-unix-domain-socket

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=\"$TCP_PGHOST\" -c hba_file=$HBA_FILE -k $UNIX_PGHOST -c log_statement=\"all\" " \
>> "$setuplog"

log "Creating a minimally privileged $PGUSER connection role..."
Expand All @@ -94,6 +104,7 @@ let
replica_slot="replica_$RANDOM"
replica_dir="$tmpdir/$replica_slot"
replica_host="$tmpdir/socket_$replica_slot"
replica_port=$(${randomPort})

mkdir -p "$replica_host"

Expand All @@ -106,15 +117,16 @@ let

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\" " \
pg_ctl -D "$replica_dir" -l "$replica_dblog" -w start -o "-F -c listen_addresses=\"$TCP_PGHOST\" -c port=$replica_port -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 PGREPLICAPORT="$replica_port"
export PGREPLICASLOT="$replica_slot"
export PGRST_DB_URI="postgres:///$PGDATABASE?host=$PGREPLICAHOST,$PGHOST"
export PGRST_DB_URI="postgres:///$PGDATABASE?host=$PGREPLICAHOST,$PGHOST&port=$replica_port,$PGPORT"
fi

# shellcheck disable=SC2317
Expand Down Expand Up @@ -203,24 +215,8 @@ let
withTmpDir = true;
}
''
delay="''${PGDELAY:-0ms}"
echo "delaying data to/from postgres by $delay"

REALPGHOST="$PGHOST"
export PGHOST="$tmpdir/socket"
mkdir -p "$PGHOST"

${slocat}/bin/slocat -delay "$delay" -src "$PGHOST/.s.PGSQL.5432" -dst "$REALPGHOST/.s.PGSQL.5432" &
SLOCAT_PID=$!
# shellcheck disable=SC2317
stop_slocat() {
kill "$SLOCAT_PID" || true
wait "$SLOCAT_PID" || true
}
trap stop_slocat EXIT
sleep 1 # should wait for socket file to appear instead

("$_arg_command" "''${_arg_leftovers[@]}")
proxyPort=''$(${randomPort})
(${withToxiproxyServer} ${withToxiproxyProxy} -l "$TCP_PGHOST:$proxyPort" -u "$TCP_PGHOST:$PGPORT" ${withToxiproxyLatency} "$PGDELAY" env "PGHOST=$TCP_PGHOST" "PGPORT=$proxyPort" "$_arg_command" "''${_arg_leftovers[@]}")
'';

withSlowPgrst =
Expand All @@ -239,25 +235,40 @@ let
workingDir = "/";
redirectTixFiles = false;
withTmpDir = true;
withPath = [ socat ];
}
''
delay="''${PGRST_DELAY:-0ms}"
echo "delaying data to/from PostgREST by $delay"
# Toxiproxy cannot connect to unix sockets
# so we start socat to make Pgrst available on TCP
# and another socat to make Toxiproxy available on Unix socket
# TODO maybe simply change withPgrst to start PostgREST on TCP socket
socatPort=''$(${randomPort})
proxyPort=''$(${randomPort})
slowPgrstSocket="$tmpdir/postgrest.socket"

socat TCP-LISTEN:"$socatPort",reuseaddr,fork UNIX-CONNECT:"$PGRST_SERVER_UNIX_SOCKET" &
UPSTREAM_SOCAT_PID=$!
echo "Started upstream socat"

socat UNIX-LISTEN:"$slowPgrstSocket",fork,unlink-early TCP:localhost:"$proxyPort" &
DOWNSTREAM_SOCAT_PID=$!
echo "Started downstream socat"

REAL_PGRST_SERVER_UNIX_SOCKET="$PGRST_SERVER_UNIX_SOCKET"
export PGRST_SERVER_UNIX_SOCKET="$tmpdir/postgrest.socket"

${slocat}/bin/slocat -delay "$delay" -src "$PGRST_SERVER_UNIX_SOCKET" -dst "$REAL_PGRST_SERVER_UNIX_SOCKET" &
SLOCAT_PID=$!
# shellcheck disable=SC2317
stop_slocat() {
kill "$SLOCAT_PID" || true
wait "$SLOCAT_PID" || true
stop() {
kill "$UPSTREAM_SOCAT_PID" || true
kill "$DOWNSTREAM_SOCAT_PID" || true
wait "$UPSTREAM_SOCAT_PID" || true
wait "$DOWNSTREAM_SOCAT_PID" || true
}
trap stop_slocat EXIT
sleep 1 # should wait for socket file to appear instead
trap stop EXIT

("$_arg_command" "''${_arg_leftovers[@]}")
sleep 1

# Execute command with Toxiproxy and environment having
# PGRST_SERVER_UNIX_SOCKET variable unset
# and PGRST_SERVER_HOST/PGRST_SERVER_PORT set to localhost/$proxyPort
(${withToxiproxyServer} ${withToxiproxyProxy} -l "localhost:$proxyPort" -u "localhost:$socatPort" env "PGRST_SERVER_UNIX_SOCKET=$slowPgrstSocket" "PGRST_SERVER_HOST=localhost" "PGRST_SERVER_PORT=$proxyPort" ${withToxiproxyLatency} "$PGRST_DELAY" "$_arg_command" "''${_arg_leftovers[@]}")
'';

withGit =
Expand Down Expand Up @@ -447,6 +458,117 @@ let
libraries = [ python3Packages.pandas python3Packages.tabulate python3Packages.psutil ];
}
(builtins.readFile ./monitor_pid.py);

randomPort =
writers.writePython3 "postgrest-random-port"
{
# Quick one-liner: ignore linting errors
flakeIgnore = [ "E702" "W292" "E501" ];
}
''import socket; s = socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()'';

withToxiproxyProxy =
checkedShellScript
{
name = "postgrest-with-toxiproxy-proxy";
docs = "Run <command> with Toxiproxy proxy created. Proxy name passed as TOXI_PROXY_NAME env variable.";
args =
[
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])"
"ARG_OPTIONAL_SINGLE([listen], [l], [Proxy will listen on this address])"
"ARG_OPTIONAL_SINGLE([upstream], [u], [Proxy will forward to this address])"
];
positionalCompletion = "_command";
workingDir = "/";
withPath = [ toxiproxy ];
}
''
proxyname="tp$RANDOM"
toxiproxy-cli create -l "$_arg_listen" -u "$_arg_upstream" "$proxyname"

# shellcheck disable=SC2317
stop () {
toxiproxy-cli delete "$proxyname" || true
}
trap stop EXIT

(TOXI_PROXY_NAME="$proxyname" "$_arg_command" "''${_arg_leftovers[@]}")
'';

withToxiproxyLatency =
checkedShellScript
{
name = "postgrest-with-toxiproxy-latency";
docs = "Run <command> with Toxiproxy latency toxic for both upstream and downstream";
args =
[
"ARG_POSITIONAL_SINGLE([latency], [Latency])"
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])"
"ARG_USE_ENV([TOXI_PROXY_NAME], [], [Toxiproxy proxy name to create toxic in])"
];
positionalCompletion = "_command";
workingDir = "/";
withPath = [ toxiproxy ];
}
''
proxyname="$TOXI_PROXY_NAME"
upstream_toxicname="toxic$RANDOM"
downstream_toxicname="toxic$RANDOM"
# calculate delay in milliseconds
# version accepting only milliseconds
# TODO implement delay in seconds/minutes/hours
#read -r delay unit <<<''$(echo "$_arg_latency" | sed -r 's/([0-9]+)(.*)/\1 \2/g')
delay=''$(echo "$_arg_latency" | sed -r 's/([0-9]+)(.*)/\1/g')

toxiproxy-cli toxic add -t latency --upstream -n "$upstream_toxicname" -a latency="$delay" "$proxyname"
toxiproxy-cli toxic add -t latency --downstream -n "$downstream_toxicname" -a latency="$delay" "$proxyname"

# shellcheck disable=SC2317
stop () {
toxiproxy-cli toxic delete -n "$downstream_toxicname" "$proxyname" || true
toxiproxy-cli toxic delete -n "$upstream_toxicname" "$proxyname" || true
}
trap stop EXIT

("$_arg_command" "''${_arg_leftovers[@]}")
'';

withToxiproxyServer =
let
commandName = "postgrest-with-toxiproxy-server";
in
checkedShellScript
{
name = commandName;
docs = "Run <command> with toxiproxy-server";
args =
[
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
"ARG_LEFTOVERS([command arguments])"
];
positionalCompletion = "_command";
workingDir = "/";
withPath = [ toxiproxy ];
}
''
if ! test -v TOXI_PROXY; then
export TOXI_PROXY=""
LOG_LEVEL=error toxiproxy-server&
TOXIPROXY_PID=$!
sleep 1 # give the server a moment to start

# shellcheck disable=SC2317
stop () {
kill "$TOXIPROXY_PID" || true
wait "$TOXIPROXY_PID" || true
}
trap stop EXIT
fi
("$_arg_command" "''${_arg_leftovers[@]}")
'';

in
buildToolbox
{
Expand Down
6 changes: 5 additions & 1 deletion test/io/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ def dburi():
"Postgres database connection URI."
dbname = os.environ["PGDATABASE"]
host = os.environ["PGHOST"]
port = os.environ["PGPORT"]
user = os.environ["PGUSER"]
return f"postgresql://?dbname={dbname}&host={host}&user={user}".encode()
return f"postgresql://?dbname={dbname}&host={host}&port={port}&user={user}".encode()


@pytest.fixture
Expand All @@ -19,6 +20,7 @@ def baseenv():
return {
"PGDATABASE": os.environ["PGDATABASE"],
"PGHOST": os.environ["PGHOST"],
"PGPORT": os.environ["PGPORT"],
"PGUSER": os.environ["PGUSER"],
}

Expand Down Expand Up @@ -51,6 +53,7 @@ def replicaenv(defaultenv):
**defaultenv,
**conf,
"PGHOST": os.environ["PGREPLICAHOST"] + "," + os.environ["PGHOST"],
"PGPORT": os.environ["PGREPLICAPORT"] + "," + os.environ["PGPORT"],
"PGREPLICASLOT": os.environ["PGREPLICASLOT"],
},
}
Expand All @@ -76,6 +79,7 @@ def metapostgrest():
env = {
"PGDATABASE": os.environ["PGDATABASE"],
"PGHOST": os.environ["PGHOST"],
"PGPORT": os.environ["PGPORT"],
"PGUSER": role,
"PGRST_DB_ANON_ROLE": role,
"PGRST_DB_CONFIG": "true",
Expand Down
2 changes: 1 addition & 1 deletion test/io/test_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ def relativeSeconds(sec):

def test_fail_with_invalid_password(defaultenv):
"Connecting with an invalid password should fail without retries."
uri = f'postgresql://?dbname={defaultenv["PGDATABASE"]}&host={defaultenv["PGHOST"]}&user=some_protected_user&password=invalid_pass'
uri = f'postgresql://?dbname={defaultenv["PGDATABASE"]}&host={defaultenv["PGHOST"]}&port={defaultenv["PGPORT"]}&user=some_protected_user&password=invalid_pass'
env = {**defaultenv, "PGRST_DB_URI": uri}
with run(env=env, wait_for_readiness=False) as postgrest:
exitCode = wait_until_exit(postgrest)
Expand Down
2 changes: 1 addition & 1 deletion test/io/test_io.py
Original file line number Diff line number Diff line change
Expand Up @@ -1653,7 +1653,7 @@ def test_log_listener_connection_start(defaultenv):
# Check for the listener start message containing host and port
# Do not check if pg version is displayed properly as it is tricky to test it
assert any(
f'"{defaultenv["PGHOST"]}:5432" and listening for database notifications on the "pgrst" channel'
f'"{defaultenv["PGHOST"]}:{defaultenv["PGPORT"]}" and listening for database notifications on the "pgrst" channel'
in line
for line in output
)
Expand Down
Loading