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,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
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+
450572in
451573buildToolbox
452574{
0 commit comments