#!/usr/bin/env bash # test_stress.sh — стресс-тест параллельного использования: # COUNT пользователей создают функции параллельно # LIST изоляция: каждый видит только свои функции # Invoke стресс: INVOKE_COUNT параллельных invoke для livetest@test.local (watched namespace) # Cleanup: удаляем namespace-ы тест-пользователей, функции livetest — через API DELETE. set -uo pipefail ROOT_DIR=$(cd "$(dirname "$0")" && pwd) source "${ROOT_DIR}/lib.sh" NS_CLEANUP_SCRIPT="${ROOT_DIR}/cleanup_test_namespaces.sh" RUN_ID=$(mk_run_id) COUNT="${1:-8}" TMP=$(mktemp -d) INVOKE_SUB="test-livetest@test.local" INVOKE_COUNT=8 declare -a SUBS=() for i in $(seq 1 "$COUNT"); do SUBS+=("test-stress-${RUN_ID}-u${i}@test.local") done declare -a INVOKE_FNS=() for i in $(seq 1 "$INVOKE_COUNT"); do INVOKE_FNS+=("t-sinv-${RUN_ID}-${i}") done cleanup() { rm -rf "$TMP" 2>/dev/null || true if [ -x "$NS_CLEANUP_SCRIPT" ] && [ "${#SUBS[@]}" -gt 0 ]; then KUBECTL_DIRECT=1 "$NS_CLEANUP_SCRIPT" "${SUBS[@]}" >/dev/null 2>&1 || true fi for fn in "${INVOKE_FNS[@]}"; do curl -s -X DELETE "${BASE}/functions/${fn}" -H "X-Auth-Token: ${INVOKE_SUB}" > /dev/null 2>&1 || true done } trap cleanup EXIT echo "" echo "========================================" echo " test_stress.sh run=$RUN_ID users=$COUNT invoke_parallel=$INVOKE_COUNT" echo "========================================" JS_CODE='module.exports = async function() { return { status: 200, body: "stress-ok" }; }' # ── Шаг 1: параллельное создание ───────────────────────────────────────────── echo "" echo ">>> [1/3] $COUNT пользователей создают функции параллельно..." for i in $(seq 1 "$COUNT"); do ( SUB="${SUBS[$((i-1))]}" FN="stress-fn-${RUN_ID}" R=$(curl -s -w "\n%{http_code}" --max-time 60 -X POST "${BASE}/functions" \ -H "X-Auth-Token: ${SUB}" -H "Content-Type: application/json" \ -d "{\"name\":\"${FN}\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$JS_CODE" | json_escape)}" \ 2>/dev/null) CODE=$(echo "$R" | tail -1) echo "$i:$CODE" > "${TMP}/create-$i" ) & done wait CREATE_OK=0 CREATE_FAIL=0 for i in $(seq 1 "$COUNT"); do CODE=$(cut -d: -f2 "${TMP}/create-$i" 2>/dev/null || echo "000") if [ "$CODE" = "201" ] || [ "$CODE" = "200" ]; then CREATE_OK=$((CREATE_OK+1)) else CREATE_FAIL=$((CREATE_FAIL+1)) echo " user$i: CREATE HTTP $CODE" fi done [ "$CREATE_FAIL" -eq 0 ] \ && pass "Параллельный CREATE: все $COUNT пользователей → 201" \ || fail "Параллельный CREATE: $CREATE_FAIL/$COUNT упали" # ── Шаг 2: изоляция LIST ───────────────────────────────────────────────────── echo "" echo ">>> [2/3] Изоляция: каждый видит только свою функцию" ISO_OK=0 for i in $(seq 1 "$COUNT"); do SUB="${SUBS[$((i-1))]}" FN="stress-fn-${RUN_ID}" LIST=$(curl -s --max-time 20 "${BASE}/functions" -H "X-Auth-Token: ${SUB}") OWN_COUNT=$(printf '%s' "$LIST" | python3 -c " import json, sys try: fns = json.load(sys.stdin) print(sum(1 for f in fns if f.get('metadata',{}).get('name','') == '${FN}')) except: print(0)" 2>/dev/null) TOTAL_COUNT=$(json_len "$LIST") if [ "${OWN_COUNT:-0}" -eq 1 ] && [ "${TOTAL_COUNT:-0}" -eq 1 ]; then ISO_OK=$((ISO_OK+1)) else echo " user$i: own=$OWN_COUNT total=$TOTAL_COUNT" fi done [ "$ISO_OK" -eq "$COUNT" ] \ && pass "Изоляция: все $COUNT пользователей видят только свои функции" \ || fail "Изоляция: только $ISO_OK/$COUNT OK" # ── Шаг 3: параллельный invoke (livetest — watched namespace) ───────────────── echo "" echo ">>> [3/3] $INVOKE_COUNT параллельных invoke для livetest@test.local" INV_JS='module.exports = async function() { return { status: 200, body: "sinv-ok" }; }' for i in $(seq 1 "$INVOKE_COUNT"); do FN_I="${INVOKE_FNS[$((i-1))]}" curl -s -X POST "${BASE}/functions" \ -H "X-Auth-Token: ${INVOKE_SUB}" -H "Content-Type: application/json" \ -d "{\"name\":\"${FN_I}\",\"language\":\"nodejs\",\"code\":$(printf '%s' "$INV_JS" | json_escape)}" > /dev/null done echo "Ждём прогрева $INVOKE_COUNT функций..." for i in $(seq 1 "$INVOKE_COUNT"); do FN_I="${INVOKE_FNS[$((i-1))]}" for try in $(seq 1 12); do R=$(curl -s --max-time 30 -X POST "${BASE}/functions/${FN_I}/invoke" \ -H "X-Auth-Token: ${INVOKE_SUB}" -H "Content-Type: application/json" -d '{}') ST=$(json_field "$R" status) [ "$ST" = "200" ] && break echo " прогрев функции $i/$INVOKE_COUNT: попытка $try/12..." sleep 5 done echo " функция $i/$INVOKE_COUNT — готова" done for i in $(seq 1 "$INVOKE_COUNT"); do ( FN_I="${INVOKE_FNS[$((i-1))]}" R=$(curl -s --max-time 30 -X POST "${BASE}/functions/${FN_I}/invoke" \ -H "X-Auth-Token: ${INVOKE_SUB}" -H "Content-Type: application/json" -d '{}') ST=$(json_field "$R" status) echo "$i:$ST" > "${TMP}/sinvoke-$i" ) & done wait SINV_OK=0 SINV_FAIL=0 for i in $(seq 1 "$INVOKE_COUNT"); do CODE=$(cut -d: -f2 "${TMP}/sinvoke-$i" 2>/dev/null || echo "MISS") [ "$CODE" = "200" ] && SINV_OK=$((SINV_OK+1)) || SINV_FAIL=$((SINV_FAIL+1)) done [ "$SINV_FAIL" -le 1 ] \ && pass "Параллельный INVOKE (livetest): $SINV_OK/$INVOKE_COUNT OK" \ || fail "Параллельный INVOKE (livetest): $SINV_FAIL/$INVOKE_COUNT упали" print_summary