155 lines
5.9 KiB
Bash
Executable file
155 lines
5.9 KiB
Bash
Executable file
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
ROOT=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
|
KUBECONFIG=${KUBECONFIG:-/home/worsch/.kube/config-railiance01}
|
|
KUBECTL_BIN=${KUBECTL_BIN:-kubectl}
|
|
EXPECTED_CLUSTER_UID=${RAILIANCE01_CLUSTER_UID:-a553c742-0115-43d4-99a4-a5ca56fe0786}
|
|
NAMESPACE=apps-pg-isolation-probe
|
|
DB_HOST=apps-pg-rw.databases.svc.cluster.local
|
|
K=("$KUBECTL_BIN" --kubeconfig="$KUBECONFIG")
|
|
pass=0
|
|
fail=0
|
|
namespace_created=0
|
|
|
|
cleanup() {
|
|
if [[ "$namespace_created" == 1 && "${KEEP:-0}" != 1 ]]; then
|
|
"${K[@]}" delete namespace "$NAMESPACE" --wait=false >/dev/null 2>&1 || true
|
|
fi
|
|
}
|
|
trap cleanup EXIT
|
|
|
|
record() {
|
|
local outcome=$1 name=$2 detail=${3:-}
|
|
if [[ "$outcome" == pass ]]; then
|
|
pass=$((pass + 1))
|
|
printf 'PASS|%s%s\n' "$name" "${detail:+|$detail}"
|
|
else
|
|
fail=$((fail + 1))
|
|
printf 'FAIL|%s%s\n' "$name" "${detail:+|$detail}"
|
|
fi
|
|
}
|
|
|
|
psql_as() {
|
|
local pod=$1 role=$2 database=$3 sql=$4
|
|
"${K[@]}" exec -n "$NAMESPACE" "$pod" -- \
|
|
psql -X -v ON_ERROR_STOP=1 -At -h "$DB_HOST" -U "$role" -d "$database" -c "$sql"
|
|
}
|
|
|
|
psql_super() {
|
|
local sql=$1
|
|
"${K[@]}" exec -n databases apps-pg-1 -c postgres -- \
|
|
psql -X -v ON_ERROR_STOP=1 -U postgres -d postgres -At -c "$sql"
|
|
}
|
|
|
|
expect_value() {
|
|
local name=$1 pod=$2 role=$3 database=$4 sql=$5 expected=$6 got
|
|
if ! got=$(psql_as "$pod" "$role" "$database" "$sql" 2>&1); then
|
|
record fail "$name" "query failed"
|
|
elif [[ "$got" == "$expected" ]]; then
|
|
record pass "$name" "$got"
|
|
else
|
|
record fail "$name" "expected=$expected got=$got"
|
|
fi
|
|
}
|
|
|
|
expect_denied() {
|
|
local name=$1 pod=$2 role=$3 database=$4 sql=$5
|
|
if psql_as "$pod" "$role" "$database" "$sql" >/dev/null 2>&1; then
|
|
record fail "$name" "attempt succeeded"
|
|
else
|
|
record pass "$name" "denied"
|
|
fi
|
|
}
|
|
|
|
copy_secret() {
|
|
local name=$1
|
|
"${K[@]}" get secret "$name" -n databases -o json \
|
|
| jq --arg ns "$NAMESPACE" \
|
|
'del(.metadata.annotations, .metadata.creationTimestamp, .metadata.resourceVersion,
|
|
.metadata.uid, .metadata.ownerReferences, .metadata.managedFields) |
|
|
.metadata.namespace=$ns' \
|
|
| "${K[@]}" apply -f - >/dev/null
|
|
}
|
|
|
|
measure_vergabe_ms() {
|
|
local start end
|
|
start=$(date +%s%3N)
|
|
for _ in 1 2 3 4 5; do
|
|
psql_as vergabe-probe vergabe vergabe_db 'SELECT 1' >/dev/null
|
|
done
|
|
end=$(date +%s%3N)
|
|
echo $((end - start))
|
|
}
|
|
|
|
uid=$("${K[@]}" get namespace kube-system -o jsonpath='{.metadata.uid}')
|
|
if [[ "$uid" != "$EXPECTED_CLUSTER_UID" ]]; then
|
|
echo "REFUSING: expected railiance01 UID $EXPECTED_CLUSTER_UID, got $uid" >&2
|
|
exit 2
|
|
fi
|
|
if "${K[@]}" get namespace "$NAMESPACE" >/dev/null 2>&1; then
|
|
echo "REFUSING: probe namespace already exists: $NAMESPACE" >&2
|
|
exit 2
|
|
fi
|
|
|
|
"${K[@]}" apply -f "$ROOT/helm/apps-pg-isolation-probe.yaml" >/dev/null
|
|
namespace_created=1
|
|
copy_secret coulomb-social-app-credentials
|
|
copy_secret vergabe-app-credentials
|
|
"${K[@]}" wait --for=condition=Ready pod/coulomb-probe pod/vergabe-probe \
|
|
-n "$NAMESPACE" --timeout=60s >/dev/null
|
|
|
|
echo 'SECTION|consumer-boundary'
|
|
expect_value 'coulomb own database connects' coulomb-probe coulomb_social coulomb_social_db 'SELECT 1' 1
|
|
expect_value 'vergabe own database connects' vergabe-probe vergabe vergabe_db 'SELECT 1' 1
|
|
expect_denied 'coulomb cannot connect to vergabe database' coulomb-probe coulomb_social vergabe_db 'SELECT 1'
|
|
expect_denied 'vergabe cannot connect to coulomb database' vergabe-probe vergabe coulomb_social_db 'SELECT 1'
|
|
expect_denied 'coulomb cannot connect to platform bootstrap database' coulomb-probe coulomb_social apps_meta 'SELECT 1'
|
|
expect_denied 'consumer cannot alter peer role' coulomb-probe coulomb_social coulomb_social_db 'ALTER ROLE vergabe CONNECTION LIMIT 999'
|
|
expect_value 'coulomb sees its own table' coulomb-probe coulomb_social coulomb_social_db 'SELECT count(*) FROM members_user' 1
|
|
expect_value 'coulomb statement timeout binds' coulomb-probe coulomb_social coulomb_social_db 'SHOW statement_timeout' 15s
|
|
expect_value 'coulomb idle transaction timeout binds' coulomb-probe coulomb_social coulomb_social_db 'SHOW idle_in_transaction_session_timeout' 15s
|
|
expect_value 'coulomb connection limit is 20' coulomb-probe coulomb_social coulomb_social_db "SELECT rolconnlimit FROM pg_roles WHERE rolname=current_user" 20
|
|
expect_denied 'overlong statement is terminated' coulomb-probe coulomb_social coulomb_social_db 'SELECT pg_sleep(20)'
|
|
|
|
echo 'SECTION|noisy-neighbour-connection-saturation'
|
|
baseline_ms=$(measure_vergabe_ms)
|
|
existing=$(psql_super "SELECT count(*) FROM pg_stat_activity WHERE usename='coulomb_social'")
|
|
hold=$((20 - existing))
|
|
if (( hold < 1 )); then
|
|
record fail 'saturation setup has free role slots' "existing=$existing"
|
|
else
|
|
"${K[@]}" exec -n "$NAMESPACE" coulomb-probe -- sh -c '
|
|
count=$1
|
|
i=1
|
|
while [ "$i" -le "$count" ]; do
|
|
psql -X -v ON_ERROR_STOP=1 -At -h "$2" -U coulomb_social -d coulomb_social_db -c "\! sleep 25" >/dev/null 2>&1 &
|
|
i=$((i + 1))
|
|
done
|
|
wait
|
|
' sh "$hold" "$DB_HOST" &
|
|
saturator_pid=$!
|
|
|
|
saturated=0
|
|
for _ in $(seq 1 50); do
|
|
current=$(psql_super "SELECT count(*) FROM pg_stat_activity WHERE usename='coulomb_social'")
|
|
if [[ "$current" == 20 ]]; then saturated=1; break; fi
|
|
sleep 0.2
|
|
done
|
|
if [[ "$saturated" == 1 ]]; then
|
|
record pass 'coulomb reaches its declared connection ceiling' 'connections=20'
|
|
else
|
|
record fail 'coulomb reaches its declared connection ceiling' "connections=$current"
|
|
fi
|
|
expect_denied 'coulomb excess connection is rejected' coulomb-probe coulomb_social coulomb_social_db 'SELECT 1'
|
|
if during_ms=$(measure_vergabe_ms); then
|
|
record pass 'vergabe remains available during peer saturation' "baseline_5q_ms=$baseline_ms saturated_5q_ms=$during_ms"
|
|
else
|
|
during_ms=unavailable
|
|
record fail 'vergabe remains available during peer saturation'
|
|
fi
|
|
wait "$saturator_pid" || true
|
|
fi
|
|
|
|
echo "SUMMARY|passed=$pass|failed=$fail|baseline_5q_ms=$baseline_ms|saturated_5q_ms=${during_ms:-not-run}"
|
|
(( fail == 0 ))
|