Skip to content

Commit 73b7a57

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 2861b35 commit 73b7a57

7 files changed

Lines changed: 166 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: 159 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,16 @@ 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"
127+
export PGREPLICAPORT="$replica_port"
116128
export PGREPLICASLOT="$replica_slot"
117-
export PGRST_DB_URI="postgres:///$PGDATABASE?host=$PGREPLICAHOST,$PGHOST"
129+
export PGRST_DB_URI="postgres:///$PGDATABASE?host=$PGREPLICAHOST,$PGHOST&port=$replica_port,$PGPORT"
118130
fi
119131
120132
# shellcheck disable=SC2317
@@ -203,24 +215,8 @@ let
203215
withTmpDir = true;
204216
}
205217
''
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[@]}")
218+
proxyPort=''$(${randomPort})
219+
(${withToxiproxyServer} ${withToxiproxyProxy} -l "$TCP_PGHOST:$proxyPort" -u "$TCP_PGHOST:$PGPORT" ${withToxiproxyLatency} "$PGDELAY" env "PGHOST=$TCP_PGHOST" "PGPORT=$proxyPort" "$_arg_command" "''${_arg_leftovers[@]}")
224220
'';
225221

226222
withSlowPgrst =
@@ -239,25 +235,40 @@ let
239235
workingDir = "/";
240236
redirectTixFiles = false;
241237
withTmpDir = true;
238+
withPath = [ socat ];
242239
}
243240
''
244-
delay="''${PGRST_DELAY:-0ms}"
245-
echo "delaying data to/from PostgREST by $delay"
241+
# Toxiproxy cannot connect to unix sockets
242+
# so we start socat to make Pgrst available on TCP
243+
# and another socat to make Toxiproxy available on Unix socket
244+
# TODO maybe simply change withPgrst to start PostgREST on TCP socket
245+
socatPort=''$(${randomPort})
246+
proxyPort=''$(${randomPort})
247+
slowPgrstSocket="$tmpdir/postgrest.socket"
248+
249+
socat TCP-LISTEN:"$socatPort",reuseaddr,fork UNIX-CONNECT:"$PGRST_SERVER_UNIX_SOCKET" &
250+
UPSTREAM_SOCAT_PID=$!
251+
echo "Started upstream socat"
252+
253+
socat UNIX-LISTEN:"$slowPgrstSocket",fork,unlink-early TCP:localhost:"$proxyPort" &
254+
DOWNSTREAM_SOCAT_PID=$!
255+
echo "Started downstream socat"
246256
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=$!
252257
# shellcheck disable=SC2317
253-
stop_slocat() {
254-
kill "$SLOCAT_PID" || true
255-
wait "$SLOCAT_PID" || true
258+
stop() {
259+
kill "$UPSTREAM_SOCAT_PID" || true
260+
kill "$DOWNSTREAM_SOCAT_PID" || true
261+
wait "$UPSTREAM_SOCAT_PID" || true
262+
wait "$DOWNSTREAM_SOCAT_PID" || true
256263
}
257-
trap stop_slocat EXIT
258-
sleep 1 # should wait for socket file to appear instead
264+
trap stop EXIT
259265
260-
("$_arg_command" "''${_arg_leftovers[@]}")
266+
sleep 1
267+
268+
# Execute command with Toxiproxy and environment having
269+
# PGRST_SERVER_UNIX_SOCKET variable unset
270+
# and PGRST_SERVER_HOST/PGRST_SERVER_PORT set to localhost/$proxyPort
271+
(${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[@]}")
261272
'';
262273

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

test/io/conftest.py

Lines changed: 5 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

@@ -51,6 +53,7 @@ def replicaenv(defaultenv):
5153
**defaultenv,
5254
**conf,
5355
"PGHOST": os.environ["PGREPLICAHOST"] + "," + os.environ["PGHOST"],
56+
"PGPORT": os.environ["PGREPLICAPORT"] + "," + os.environ["PGPORT"],
5457
"PGREPLICASLOT": os.environ["PGREPLICASLOT"],
5558
},
5659
}
@@ -76,6 +79,7 @@ def metapostgrest():
7679
env = {
7780
"PGDATABASE": os.environ["PGDATABASE"],
7881
"PGHOST": os.environ["PGHOST"],
82+
"PGPORT": os.environ["PGPORT"],
7983
"PGUSER": role,
8084
"PGRST_DB_ANON_ROLE": role,
8185
"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)