diff --git a/tests/stress_test.sh b/tests/stress_test.sh index 94f2d1d..3d8ca45 100644 --- a/tests/stress_test.sh +++ b/tests/stress_test.sh @@ -1,6 +1,7 @@ #!/bin/bash # stress_test.sh — стресс-тест shared-sqs: конкурентность, Redis resilience, падение пода # Created: 2026-04-11 +# Updated: 2026-04-11 — fix: правильный формат queue URL, парсинг tenant API response # Покрывает: # 1. Параллельная отправка сообщений (N воркеров, M сообщений) # 2. Параллельное чтение и удаление (гонка за сообщения) @@ -36,6 +37,11 @@ TMPDIR_STRESS=$(mktemp -d /tmp/sqs_stress_XXXXX) # ХЕЛПЕРЫ # ═══════════════════════════════════════════ +# json_field — извлекает значение поля из JSON (без зависимости от jq) +json_field() { + python3 -c "import sys,json; print(json.load(sys.stdin).get('$1',''))" 2>/dev/null +} + check() { local name="$1" output="$2" pattern="$3" TOTAL=$((TOTAL + 1)) @@ -152,31 +158,43 @@ echo "╚═══════════════════════ echo "" # ═══════════════════════════════════════════════════════════ -# 1. ПОДГОТОВКА — создаём тенантов и очереди +# 1. ПОДГОТОВКА — создаём тенантов и очереди через Admin API # ═══════════════════════════════════════════════════════════ echo "═══ 1. Подготовка — тенанты и очереди ═══" -# Создаём тенантов для теста +# Создаём тенантов — API сам генерирует access_key/secret_key declare -a TENANT_AK declare -a TENANT_SK +declare -a TENANT_ID for i in $(seq 1 $TENANT_COUNT); do - R=$(admin_api POST "/admin/tenants" "{\"name\":\"stress-t${i}-${TS}\",\"access_key\":\"STRESSAK${i}${TS}\",\"secret_key\":\"STRESSSK${i}${TS}\"}") - TENANT_AK[$i]="STRESSAK${i}${TS}" - TENANT_SK[$i]="STRESSSK${i}${TS}" - echo " Тенант ${i}: AK=${TENANT_AK[$i]}" + R=$(admin_api POST "/admin/tenants" "{\"name\":\"stress-t${i}-${TS}\",\"max_queues\":50}") + TENANT_AK[$i]=$(echo "$R" | json_field access_key) + TENANT_SK[$i]=$(echo "$R" | json_field secret_key) + TENANT_ID[$i]=$(echo "$R" | json_field id) + echo " Тенант ${i}: ID=${TENANT_ID[$i]} AK=${TENANT_AK[$i]}" + if [[ -z "${TENANT_AK[$i]}" ]]; then + echo " ⛔ ОШИБКА: не удалось создать тенанта ${i}. Ответ: $R" + exit 1 + fi done -# Очередь для concurrency-теста +# qurl — формирует правильный Queue URL: BASE_URL/TID/QUEUE_NAME +# Формат: https://qu.kube5s.ru/{tenant_id}/{queue_name} +qurl() { + echo "${BASE_URL}/${1}/${2}" +} + +# Очередь для concurrency-теста (одно имя, но у каждого тенанта — своя изолированная) STRESS_Q="stress-concurrent-${TS}" for i in $(seq 1 $TENANT_COUNT); do R=$(sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" create-queue --queue-name "$STRESS_Q") + echo " Тенант ${i}: создана очередь $STRESS_Q → $(echo "$R" | json_field QueueUrl)" done -echo " Очередь: $STRESS_Q создана для $TENANT_COUNT тенантов" # Дополнительная очередь для burst-теста BURST_Q="stress-burst-${TS}" R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" create-queue --queue-name "$BURST_Q") -echo " Очередь: $BURST_Q (burst)" +echo " Burst очередь: $BURST_Q" echo "" # ═══════════════════════════════════════════════════════════ @@ -187,14 +205,21 @@ echo "═══ 2. Конкурентная отправка: ${CONCURRENT_WORKE TOTAL_MSGS=$((CONCURRENT_WORKERS * MESSAGES_PER_WORKER)) echo " Всего будет отправлено: $TOTAL_MSGS сообщений" +# Сохраняем credentials первого тенанта для удобства в subshell +T1_AK="${TENANT_AK[1]}" +T1_SK="${TENANT_SK[1]}" +T1_ID="${TENANT_ID[1]}" +STRESS_QURL=$(qurl "$T1_ID" "$STRESS_Q") +BURST_QURL=$(qurl "$T1_ID" "$BURST_Q") + # Запускаем воркеров параллельно for w in $(seq 1 $CONCURRENT_WORKERS); do ( ok=0 fail=0 for m in $(seq 1 $MESSAGES_PER_WORKER); do - R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \ - --queue-url "${BASE_URL}/queue/${STRESS_Q}" \ + R=$(sqs "$T1_AK" "$T1_SK" send-message \ + --queue-url "$STRESS_QURL" \ --message-body "worker-${w}-msg-${m}-${TS}" 2>&1) if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)) @@ -223,8 +248,8 @@ check_eq "Все сообщения отправлены" "$SEND_OK" "$TOTAL_MSG # Проверяем через GetQueueAttributes — ApproximateNumberOfMessages sleep 2 -R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" get-queue-attributes \ - --queue-url "${BASE_URL}/queue/${STRESS_Q}" \ +R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes \ + --queue-url "$STRESS_QURL" \ --attribute-names ApproximateNumberOfMessages 2>&1) APPROX=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+') echo " Очередь сообщает: ~${APPROX} сообщений" @@ -244,8 +269,8 @@ for w in $(seq 1 $CONCURRENT_WORKERS); do empty_rounds=0 # Читаем пока есть сообщения (max 10 пустых подряд — выход) while [[ $empty_rounds -lt 10 ]]; do - R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" receive-message \ - --queue-url "${BASE_URL}/queue/${STRESS_Q}" \ + R=$(sqs "$T1_AK" "$T1_SK" receive-message \ + --queue-url "$STRESS_QURL" \ --max-number-of-messages 10 \ --wait-time-seconds 1 2>&1) # Считаем полученные @@ -259,8 +284,8 @@ for w in $(seq 1 $CONCURRENT_WORKERS); do # Удаляем каждое полученное HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+') while IFS= read -r handle; do - DR=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" delete-message \ - --queue-url "${BASE_URL}/queue/${STRESS_Q}" \ + DR=$(sqs "$T1_AK" "$T1_SK" delete-message \ + --queue-url "$STRESS_QURL" \ --receipt-handle "$handle" 2>&1) deleted=$((deleted + 1)) done <<< "$HANDLES" @@ -289,8 +314,8 @@ check_eq "Удалено = отправлено (нет дубликатов)" " # Очередь должна быть пуста sleep 2 -R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" get-queue-attributes \ - --queue-url "${BASE_URL}/queue/${STRESS_Q}" \ +R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes \ + --queue-url "$STRESS_QURL" \ --attribute-names ApproximateNumberOfMessages 2>&1) APPROX=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+') check_eq "Очередь пуста после чтения" "${APPROX:-0}" "0" @@ -302,13 +327,24 @@ echo "" echo "═══ 4. Multi-tenant изоляция: ${TENANT_COUNT} тенантов, параллельная нагрузка ═══" MSGS_PER_TENANT=30 -# Каждый тенант отправляет уникальные сообщения в одноимённую очередь +# Сохраняем массивы в файлы — subshell не наследует bash-массивы надёжно +for i in $(seq 1 $TENANT_COUNT); do + echo "${TENANT_AK[$i]}" > "${TMPDIR_STRESS}/tak_${i}" + echo "${TENANT_SK[$i]}" > "${TMPDIR_STRESS}/tsk_${i}" + echo "${TENANT_ID[$i]}" > "${TMPDIR_STRESS}/tid_${i}" +done + +# Каждый тенант отправляет уникальные сообщения в свою изолированную очередь for i in $(seq 1 $TENANT_COUNT); do ( + tak=$(cat "${TMPDIR_STRESS}/tak_${i}") + tsk=$(cat "${TMPDIR_STRESS}/tsk_${i}") + tid=$(cat "${TMPDIR_STRESS}/tid_${i}") + my_qurl="${BASE_URL}/${tid}/${STRESS_Q}" ok=0 for m in $(seq 1 $MSGS_PER_TENANT); do - R=$(sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" send-message \ - --queue-url "${BASE_URL}/queue/${STRESS_Q}" \ + R=$(sqs "$tak" "$tsk" send-message \ + --queue-url "$my_qurl" \ --message-body "tenant-${i}-isolation-${m}" 2>&1) if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi done @@ -320,12 +356,16 @@ wait # Каждый тенант читает — должен видеть ТОЛЬКО свои сообщения for i in $(seq 1 $TENANT_COUNT); do ( + tak=$(cat "${TMPDIR_STRESS}/tak_${i}") + tsk=$(cat "${TMPDIR_STRESS}/tsk_${i}") + tid=$(cat "${TMPDIR_STRESS}/tid_${i}") + my_qurl="${BASE_URL}/${tid}/${STRESS_Q}" own=0 foreign=0 empty_rounds=0 while [[ $empty_rounds -lt 5 ]]; do - R=$(sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" receive-message \ - --queue-url "${BASE_URL}/queue/${STRESS_Q}" \ + R=$(sqs "$tak" "$tsk" receive-message \ + --queue-url "$my_qurl" \ --max-number-of-messages 10 \ --wait-time-seconds 1 2>&1) MSG_COUNT=$(echo "$R" | grep -c '"Body"' || true) @@ -345,8 +385,8 @@ for i in $(seq 1 $TENANT_COUNT); do # Удаляем HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+') while IFS= read -r handle; do - sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" delete-message \ - --queue-url "${BASE_URL}/queue/${STRESS_Q}" \ + sqs "$tak" "$tsk" delete-message \ + --queue-url "$my_qurl" \ --receipt-handle "$handle" >/dev/null 2>&1 done <<< "$HANDLES" done @@ -378,8 +418,8 @@ echo "═══ 5. Burst: ${BURST_SIZE} сообщений одновремен # Отправляем BURST_SIZE сообщений максимально параллельно for b in $(seq 1 $BURST_SIZE); do ( - R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \ - --queue-url "${BASE_URL}/queue/${BURST_Q}" \ + R=$(sqs "$T1_AK" "$T1_SK" send-message \ + --queue-url "$BURST_QURL" \ --message-body "burst-${b}-${TS}" 2>&1) if echo "$R" | grep -q "MessageId"; then echo "1"; else echo "0"; fi \ > "${TMPDIR_STRESS}/burst_${b}.result" @@ -400,8 +440,8 @@ check_ge "Burst: >= 90% успешно" "$BURST_OK" "$((BURST_SIZE * 90 / 100))" # Проверяем что очередь получила все sleep 3 -R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" get-queue-attributes \ - --queue-url "${BASE_URL}/queue/${BURST_Q}" \ +R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes \ + --queue-url "$BURST_QURL" \ --attribute-names ApproximateNumberOfMessages 2>&1) BURST_APPROX=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+') echo " Очередь burst: ~${BURST_APPROX} сообщений" @@ -420,8 +460,8 @@ echo " Сообщений в burst-очереди до kill: $BEFORE_KILL" # Отправляем ещё пачку — чтобы были "свежие" данные в Redis EXTRA_MSGS=15 for m in $(seq 1 $EXTRA_MSGS); do - sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \ - --queue-url "${BASE_URL}/queue/${BURST_Q}" \ + sqs "$T1_AK" "$T1_SK" send-message \ + --queue-url "$BURST_QURL" \ --message-body "pre-kill-${m}-${TS}" >/dev/null 2>&1 done EXPECTED_AFTER=$((BEFORE_KILL + EXTRA_MSGS)) @@ -441,8 +481,8 @@ wait_for_healthy # Проверяем что burst-очередь восстановилась из Redis sleep 3 -R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" get-queue-attributes \ - --queue-url "${BASE_URL}/queue/${BURST_Q}" \ +R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes \ + --queue-url "$BURST_QURL" \ --attribute-names ApproximateNumberOfMessages 2>&1) AFTER_KILL=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+') echo " Сообщений после рестарта: ${AFTER_KILL:-0}" @@ -518,8 +558,8 @@ echo " NetworkPolicy удалена — Redis доступен снова" sleep 5 # Проверяем что после восстановления Redis сервис работает -R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \ - --queue-url "${BASE_URL}/queue/${BURST_Q}" \ +R=$(sqs "$T1_AK" "$T1_SK" send-message \ + --queue-url "$BURST_QURL" \ --message-body "after-redis-recovery-${TS}" 2>&1) check "Сервис работает после восстановления Redis" "$R" "MessageId" echo "" @@ -530,12 +570,13 @@ echo "" echo "═══ 8. Смешанная нагрузка: send + receive + delete одновременно ═══" MIXED_Q="stress-mixed-${TS}" -R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" create-queue --queue-name "$MIXED_Q") +R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$MIXED_Q") +MIXED_QURL=$(qurl "$T1_ID" "$MIXED_Q") # Заполняем 50 сообщений for m in $(seq 1 50); do - sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \ - --queue-url "${BASE_URL}/queue/${MIXED_Q}" \ + sqs "$T1_AK" "$T1_SK" send-message \ + --queue-url "$MIXED_QURL" \ --message-body "mixed-init-${m}" >/dev/null 2>&1 done echo " Начальная загрузка: 50 сообщений" @@ -549,8 +590,8 @@ for w in $(seq 1 5); do end=$((SECONDS + MIXED_DURATION)) ok=0 while [[ $SECONDS -lt $end ]]; do - R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \ - --queue-url "${BASE_URL}/queue/${MIXED_Q}" \ + R=$(sqs "$T1_AK" "$T1_SK" send-message \ + --queue-url "$MIXED_QURL" \ --message-body "mixed-send-${w}-$(date +%s%N)" 2>&1) if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi done @@ -564,14 +605,14 @@ for w in $(seq 1 5); do end=$((SECONDS + MIXED_DURATION)) recv=0 while [[ $SECONDS -lt $end ]]; do - R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" receive-message \ - --queue-url "${BASE_URL}/queue/${MIXED_Q}" \ + R=$(sqs "$T1_AK" "$T1_SK" receive-message \ + --queue-url "$MIXED_QURL" \ --max-number-of-messages 10 --wait-time-seconds 1 2>&1) HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+') while IFS= read -r handle; do [[ -z "$handle" ]] && continue - sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" delete-message \ - --queue-url "${BASE_URL}/queue/${MIXED_Q}" \ + sqs "$T1_AK" "$T1_SK" delete-message \ + --queue-url "$MIXED_QURL" \ --receipt-handle "$handle" >/dev/null 2>&1 recv=$((recv + 1)) done <<< "$HANDLES" @@ -587,8 +628,8 @@ for w in $(seq 1 2); do ok=0 fail=0 while [[ $SECONDS -lt $end ]]; do - R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" get-queue-attributes \ - --queue-url "${BASE_URL}/queue/${MIXED_Q}" \ + R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes \ + --queue-url "$MIXED_QURL" \ --attribute-names All 2>&1) if echo "$R" | grep -q "ApproximateNumberOfMessages"; then ok=$((ok + 1)) @@ -634,13 +675,16 @@ echo "" echo "═══ 9. Cleanup ═══" for i in $(seq 1 $TENANT_COUNT); do - sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" delete-queue \ - --queue-url "${BASE_URL}/queue/${STRESS_Q}" >/dev/null 2>&1 + tak=$(cat "${TMPDIR_STRESS}/tak_${i}") + tsk=$(cat "${TMPDIR_STRESS}/tsk_${i}") + tid=$(cat "${TMPDIR_STRESS}/tid_${i}") + sqs "$tak" "$tsk" delete-queue \ + --queue-url "${BASE_URL}/${tid}/${STRESS_Q}" >/dev/null 2>&1 done -sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" delete-queue \ - --queue-url "${BASE_URL}/queue/${BURST_Q}" >/dev/null 2>&1 -sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" delete-queue \ - --queue-url "${BASE_URL}/queue/${MIXED_Q}" >/dev/null 2>&1 +sqs "$T1_AK" "$T1_SK" delete-queue \ + --queue-url "$BURST_QURL" >/dev/null 2>&1 +sqs "$T1_AK" "$T1_SK" delete-queue \ + --queue-url "$MIXED_QURL" >/dev/null 2>&1 echo " Очереди и тенанты очищены" echo ""