Skip to content

Commit 2f08484

Browse files
committed
test(refactor): Replace slocat with Toxiproxy
DISCLAIMER: This commit was authored entirely by a human without the assistance of LLMs.
1 parent e2ad9f8 commit 2f08484

7 files changed

Lines changed: 163 additions & 55 deletions

File tree

default.nix

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ let
4444
allOverlays.checked-shell-script
4545
allOverlays.gitignore
4646
(allOverlays.haskell-packages { inherit compiler; })
47-
allOverlays.slocat
4847
];
4948

5049
# Evaluated expression of the Nixpkgs repository.

nix/overlays/default.nix

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,4 @@
33
checked-shell-script = import ./checked-shell-script;
44
gitignore = import ./gitignore.nix;
55
haskell-packages = import ./haskell-packages.nix;
6-
slocat = import ./slocat.nix;
76
}

nix/overlays/slocat.nix

Lines changed: 0 additions & 13 deletions
This file was deleted.

nix/tools/withTools.nix

Lines changed: 157 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
, postgresqlVersions
77
, postgrest
88
, python3Packages
9-
, slocat
9+
, socat
1010
, writeText
1111
, writers
12+
, toxiproxy
1213
}:
1314
let
1415
withTmpDb =
@@ -55,6 +56,8 @@ let
5556
5657
export PGDATA="$tmpdir/db"
5758
export PGHOST="$tmpdir/socket"
59+
PGPORT=$(${randomPort})
60+
export PGPORT
5861
export PGUSER
5962
export PGDATABASE
6063
export PGRST_DB_SCHEMAS
@@ -63,8 +66,15 @@ let
6366
6467
HBA_FILE="$tmpdir/pg_hba.conf"
6568
echo "local $PGDATABASE some_protected_user password" > "$HBA_FILE"
66-
echo "local $PGDATABASE all trust" >> "$HBA_FILE"
67-
echo "local replication all trust" >> "$HBA_FILE"
69+
{
70+
echo "local $PGDATABASE all trust"
71+
echo "local replication all trust"
72+
echo "host $PGDATABASE some_protected_user localhost scram-sha-256"
73+
echo "host $PGDATABASE all localhost trust"
74+
} >> "$HBA_FILE"
75+
76+
UNIX_PGHOST="$PGHOST"
77+
export TCP_PGHOST="localhost"
6878
6979
log "Initializing database cluster..."
7080
# We try to make the database cluster as independent as possible from the host
@@ -81,7 +91,7 @@ let
8191
# On MacOS, it's 104 chars
8292
# See: https://serverfault.com/questions/641347/check-if-a-path-exceeds-maximum-for-unix-domain-socket
8393
84-
pg_ctl -l "$tmpdir/db.log" -w start -o "-F -c listen_addresses=\"\" -c hba_file=$HBA_FILE -k $PGHOST -c log_statement=\"all\" " \
94+
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\" " \
8595
>> "$setuplog"
8696
8797
log "Creating a minimally privileged $PGUSER connection role..."
@@ -94,6 +104,7 @@ let
94104
replica_slot="replica_$RANDOM"
95105
replica_dir="$tmpdir/$replica_slot"
96106
replica_host="$tmpdir/socket_$replica_slot"
107+
replica_port=$(${randomPort})
97108
98109
mkdir -p "$replica_host"
99110
@@ -106,15 +117,15 @@ let
106117
107118
log "Starting replica on $replica_host"
108119
109-
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\" " \
120+
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\" " \
110121
>> "$setuplog"
111122
112123
>&2 echo "${commandName}: Replica enabled. You can connect to it with: psql 'postgres:///$PGDATABASE?host=$replica_host' -U postgres"
113124
>&2 echo "${commandName}: You can tail the replica logs with: tail -f $replica_dblog"
114125
115126
export PGREPLICAHOST="$replica_host"
116127
export PGREPLICASLOT="$replica_slot"
117-
export PGRST_DB_URI="postgres:///$PGDATABASE?host=$PGREPLICAHOST,$PGHOST"
128+
export PGRST_DB_URI="postgres:///$PGDATABASE?host=$PGREPLICAHOST,$PGHOST&port=$replica_port,$PGPORT"
118129
fi
119130
120131
# shellcheck disable=SC2317
@@ -203,24 +214,8 @@ let
203214
withTmpDir = true;
204215
}
205216
''
206-
delay="''${PGDELAY:-0ms}"
207-
echo "delaying data to/from postgres by $delay"
208-
209-
REALPGHOST="$PGHOST"
210-
export PGHOST="$tmpdir/socket"
211-
mkdir -p "$PGHOST"
212-
213-
${slocat}/bin/slocat -delay "$delay" -src "$PGHOST/.s.PGSQL.5432" -dst "$REALPGHOST/.s.PGSQL.5432" &
214-
SLOCAT_PID=$!
215-
# shellcheck disable=SC2317
216-
stop_slocat() {
217-
kill "$SLOCAT_PID" || true
218-
wait "$SLOCAT_PID" || true
219-
}
220-
trap stop_slocat EXIT
221-
sleep 1 # should wait for socket file to appear instead
222-
223-
("$_arg_command" "''${_arg_leftovers[@]}")
217+
proxyPort=''$(${randomPort})
218+
(${withToxiproxyServer} ${withToxiproxyProxy} -l "$TCP_PGHOST:$proxyPort" -u "$TCP_PGHOST:$PGPORT" env "PGPORT=$proxyPort" ${withToxiproxyLatency} "$PGDELAY" "$_arg_command" "''${_arg_leftovers[@]}")
224219
'';
225220

226221
withSlowPgrst =
@@ -239,25 +234,40 @@ let
239234
workingDir = "/";
240235
redirectTixFiles = false;
241236
withTmpDir = true;
237+
withPath = [ socat ];
242238
}
243239
''
244-
delay="''${PGRST_DELAY:-0ms}"
245-
echo "delaying data to/from PostgREST by $delay"
240+
# Toxiproxy cannot connect to unix sockets
241+
# so we start socat to make Pgrst available on TCP
242+
# and another socat to make Toxiproxy available on Unix socket
243+
# TODO maybe simply change withPgrst to start PostgREST on TCP socket
244+
socatPort=''$(${randomPort})
245+
proxyPort=''$(${randomPort})
246+
slowPgrstSocket="$tmpdir/postgrest.socket"
247+
248+
socat TCP-LISTEN:"$socatPort",reuseaddr,fork UNIX-CONNECT:"$PGRST_SERVER_UNIX_SOCKET" &
249+
UPSTREAM_SOCAT_PID=$!
250+
echo "Started upstream socat"
251+
252+
socat UNIX-LISTEN:"$slowPgrstSocket",fork,unlink-early TCP:localhost:"$proxyPort" &
253+
DOWNSTREAM_SOCAT_PID=$!
254+
echo "Started downstream socat"
246255
247-
REAL_PGRST_SERVER_UNIX_SOCKET="$PGRST_SERVER_UNIX_SOCKET"
248-
export PGRST_SERVER_UNIX_SOCKET="$tmpdir/postgrest.socket"
249-
250-
${slocat}/bin/slocat -delay "$delay" -src "$PGRST_SERVER_UNIX_SOCKET" -dst "$REAL_PGRST_SERVER_UNIX_SOCKET" &
251-
SLOCAT_PID=$!
252256
# shellcheck disable=SC2317
253-
stop_slocat() {
254-
kill "$SLOCAT_PID" || true
255-
wait "$SLOCAT_PID" || true
257+
stop() {
258+
kill "$UPSTREAM_SOCAT_PID" || true
259+
kill "$DOWNSTREAM_SOCAT_PID" || true
260+
wait "$UPSTREAM_SOCAT_PID" || true
261+
wait "$DOWNSTREAM_SOCAT_PID" || true
256262
}
257-
trap stop_slocat EXIT
258-
sleep 1 # should wait for socket file to appear instead
263+
trap stop EXIT
259264
260-
("$_arg_command" "''${_arg_leftovers[@]}")
265+
sleep 1
266+
267+
# Execute command with Toxiproxy and environment having
268+
# PGRST_SERVER_UNIX_SOCKET variable unset
269+
# and PGRST_SERVER_HOST/PGRST_SERVER_PORT set to localhost/$proxyPort
270+
(${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[@]}")
261271
'';
262272

263273
withGit =
@@ -447,6 +457,116 @@ let
447457
libraries = [ python3Packages.pandas python3Packages.tabulate python3Packages.psutil ];
448458
}
449459
(builtins.readFile ./monitor_pid.py);
460+
461+
randomPort =
462+
writers.writePython3 "postgrest-random-port"
463+
{
464+
# Quick one-liner: ignore linting errors
465+
flakeIgnore = [ "E702" "W292" "E501" ];
466+
}
467+
''import socket; s = socket.socket(); s.bind(("127.0.0.1", 0)); print(s.getsockname()[1]); s.close()'';
468+
469+
withToxiproxyProxy =
470+
checkedShellScript
471+
{
472+
name = "postgrest-with-toxiproxy-proxy";
473+
docs = "Run <command> with Toxiproxy proxy created. Proxy name passed as TP_PROXY_NAME env variable.";
474+
args =
475+
[
476+
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
477+
"ARG_LEFTOVERS([command arguments])"
478+
"ARG_OPTIONAL_SINGLE([listen], [l], [Proxy will listen on this address])"
479+
"ARG_OPTIONAL_SINGLE([upstream], [u], [Proxy will forward to this address])"
480+
];
481+
positionalCompletion = "_command";
482+
workingDir = "/";
483+
withPath = [ toxiproxy ];
484+
}
485+
''
486+
proxyname="tp$RANDOM"
487+
toxiproxy-cli create -l "$_arg_listen" -u "$_arg_upstream" "$proxyname"
488+
489+
# shellcheck disable=SC2317
490+
stop () {
491+
toxiproxy-cli delete "$proxyname" || true
492+
}
493+
trap stop EXIT
494+
495+
(TP_PROXY_NAME="$proxyname" "$_arg_command" "''${_arg_leftovers[@]}")
496+
'';
497+
498+
withToxiproxyLatency =
499+
checkedShellScript
500+
{
501+
name = "postgrest-with-toxiproxy-latency";
502+
docs = "Run <command> with Toxiproxy latency toxic for both upstream and downstream";
503+
args =
504+
[
505+
"ARG_POSITIONAL_SINGLE([latency], [Latency])"
506+
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
507+
"ARG_LEFTOVERS([command arguments])"
508+
];
509+
positionalCompletion = "_command";
510+
workingDir = "/";
511+
withPath = [ toxiproxy ];
512+
}
513+
''
514+
proxyname="$TP_PROXY_NAME"
515+
upstream_toxicname="toxic$RANDOM"
516+
downstream_toxicname="toxic$RANDOM"
517+
# calculate delay in milliseconds
518+
# version accepting only milliseconds
519+
# TODO implement delay in seconds/minutes/hours
520+
#read -r delay unit <<<''$(echo "$_arg_latency" | sed -r 's/([0-9]+)(.*)/\1 \2/g')
521+
delay=''$(echo "$_arg_latency" | sed -r 's/([0-9]+)(.*)/\1/g')
522+
523+
toxiproxy-cli toxic add -t latency --upstream -n "$upstream_toxicname" -a latency="$delay" "$proxyname"
524+
toxiproxy-cli toxic add -t latency --downstream -n "$downstream_toxicname" -a latency="$delay" "$proxyname"
525+
526+
# shellcheck disable=SC2317
527+
stop () {
528+
toxiproxy-cli toxic delete -n "$downstream_toxicname" "$proxyname" || true
529+
toxiproxy-cli toxic delete -n "$upstream_toxicname" "$proxyname" || true
530+
}
531+
trap stop EXIT
532+
533+
("$_arg_command" "''${_arg_leftovers[@]}")
534+
'';
535+
536+
withToxiproxyServer =
537+
let
538+
commandName = "postgrest-with-toxiproxy-server";
539+
in
540+
checkedShellScript
541+
{
542+
name = commandName;
543+
docs = "Run <command> with toxiproxy-server";
544+
args =
545+
[
546+
"ARG_POSITIONAL_SINGLE([command], [Command to run])"
547+
"ARG_LEFTOVERS([command arguments])"
548+
];
549+
positionalCompletion = "_command";
550+
workingDir = "/";
551+
withPath = [ toxiproxy ];
552+
}
553+
''
554+
if ! test -v TOXI_PROXY; then
555+
export TOXI_PROXY
556+
LOG_LEVEL=error toxiproxy-server&
557+
TOXIPROXY_PID=$!
558+
sleep 1 # give the server a moment to start
559+
560+
# shellcheck disable=SC2317
561+
stop () {
562+
kill "$TOXIPROXY_PID" || true
563+
wait "$TOXIPROXY_PID" || true
564+
}
565+
trap stop EXIT
566+
fi
567+
("$_arg_command" "''${_arg_leftovers[@]}")
568+
'';
569+
450570
in
451571
buildToolbox
452572
{

test/io/conftest.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,9 @@ def dburi():
99
"Postgres database connection URI."
1010
dbname = os.environ["PGDATABASE"]
1111
host = os.environ["PGHOST"]
12+
port = os.environ["PGPORT"]
1213
user = os.environ["PGUSER"]
13-
return f"postgresql://?dbname={dbname}&host={host}&user={user}".encode()
14+
return f"postgresql://?dbname={dbname}&host={host}&port={port}&user={user}".encode()
1415

1516

1617
@pytest.fixture
@@ -19,6 +20,7 @@ def baseenv():
1920
return {
2021
"PGDATABASE": os.environ["PGDATABASE"],
2122
"PGHOST": os.environ["PGHOST"],
23+
"PGPORT": os.environ["PGPORT"],
2224
"PGUSER": os.environ["PGUSER"],
2325
}
2426

@@ -76,6 +78,7 @@ def metapostgrest():
7678
env = {
7779
"PGDATABASE": os.environ["PGDATABASE"],
7880
"PGHOST": os.environ["PGHOST"],
81+
"PGPORT": os.environ["PGPORT"],
7982
"PGUSER": role,
8083
"PGRST_DB_ANON_ROLE": role,
8184
"PGRST_DB_CONFIG": "true",

test/io/test_auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ def relativeSeconds(sec):
165165

166166
def test_fail_with_invalid_password(defaultenv):
167167
"Connecting with an invalid password should fail without retries."
168-
uri = f'postgresql://?dbname={defaultenv["PGDATABASE"]}&host={defaultenv["PGHOST"]}&user=some_protected_user&password=invalid_pass'
168+
uri = f'postgresql://?dbname={defaultenv["PGDATABASE"]}&host={defaultenv["PGHOST"]}&port={defaultenv["PGPORT"]}&user=some_protected_user&password=invalid_pass'
169169
env = {**defaultenv, "PGRST_DB_URI": uri}
170170
with run(env=env, wait_for_readiness=False) as postgrest:
171171
exitCode = wait_until_exit(postgrest)

test/io/test_io.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1653,7 +1653,7 @@ def test_log_listener_connection_start(defaultenv):
16531653
# Check for the listener start message containing host and port
16541654
# Do not check if pg version is displayed properly as it is tricky to test it
16551655
assert any(
1656-
f'"{defaultenv["PGHOST"]}:5432" and listening for database notifications on the "pgrst" channel'
1656+
f'"{defaultenv["PGHOST"]}:{defaultenv["PGPORT"]}" and listening for database notifications on the "pgrst" channel'
16571657
in line
16581658
for line in output
16591659
)

0 commit comments

Comments
 (0)