66, postgresqlVersions
77, postgrest
88, python3Packages
9- , slocat
9+ , socat
1010, writeText
1111, writers
12+ , toxiproxy
1213} :
1314let
1415 withTmpDb =
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
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
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..."
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
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+
450570in
451571buildToolbox
452572{
0 commit comments