#!/bin/bash # stress_test.sh — стресс-тест shared-sqs: конкурентность, Redis resilience, падение пода # Created: 2026-04-11 # Покрывает: # 1. Параллельная отправка сообщений (N воркеров, M сообщений) # 2. Параллельное чтение и удаление (гонка за сообщения) # 3. Multi-tenant изоляция под нагрузкой (нет утечек между тенантами) # 4. Потеря связи с Redis — поведение при разрыве # 5. Kill пода — рестарт и восстановление данных из Redis # 6. Burst — резкий всплеск запросов # Запуск: BASE_URL=https://qu.kube5s.ru ADMIN_TOKEN=... bash tests/stress_test.sh set -uo pipefail # ═══════════════════════════════════════════ # КОНФИГУРАЦИЯ # ═══════════════════════════════════════════ BASE_URL="${BASE_URL:-https://qu.kube5s.ru}" ADMIN_TOKEN="${ADMIN_TOKEN:-sqs-admin-7a7d8bd0c060a75c198d48680f34077a}" TS=$(date +%s) NAMESPACE="shared-sqs" DEPLOY_NAME="shared-sqs" # Параметры нагрузки CONCURRENT_WORKERS=10 # параллельных воркеров MESSAGES_PER_WORKER=20 # сообщений на воркер BURST_SIZE=50 # размер burst-пакета TENANT_COUNT=3 # тенантов для multi-tenant теста PASS=0 FAIL=0 TOTAL=0 TMPDIR_STRESS=$(mktemp -d /tmp/sqs_stress_XXXXX) # ═══════════════════════════════════════════ # ХЕЛПЕРЫ # ═══════════════════════════════════════════ check() { local name="$1" output="$2" pattern="$3" TOTAL=$((TOTAL + 1)) if echo "$output" | grep -qE "$pattern"; then PASS=$((PASS + 1)) echo " ✅ $name" else FAIL=$((FAIL + 1)) echo " ❌ $name" echo " ожидалось: $pattern" echo " получено: $(echo "$output" | head -5)" fi } check_eq() { local name="$1" got="$2" want="$3" TOTAL=$((TOTAL + 1)) if [[ "$got" == "$want" ]]; then PASS=$((PASS + 1)) echo " ✅ $name ($got)" else FAIL=$((FAIL + 1)) echo " ❌ $name: ожидалось=$want, получено=$got" fi } check_ge() { local name="$1" got="$2" want="$3" TOTAL=$((TOTAL + 1)) if [[ "$got" -ge "$want" ]]; then PASS=$((PASS + 1)) echo " ✅ $name ($got >= $want)" else FAIL=$((FAIL + 1)) echo " ❌ $name: ожидалось >=$want, получено=$got" fi } # sqs — AWS CLI враппер sqs() { local ak="$1" sk="$2" shift 2 AWS_ACCESS_KEY_ID="$ak" \ AWS_SECRET_ACCESS_KEY="$sk" \ AWS_DEFAULT_REGION="us-east-1" \ aws --endpoint-url "$BASE_URL" --output json sqs "$@" 2>&1 } # admin_api — Admin REST API admin_api() { local method="$1" path="$2" body="${3:-}" if [[ -n "$body" ]]; then curl -s --max-time 20 -X "$method" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d "$body" \ "${BASE_URL}${path}" 2>&1 else curl -s --max-time 20 -X "$method" \ -H "Authorization: Bearer $ADMIN_TOKEN" \ "${BASE_URL}${path}" 2>&1 fi } # wait_for_pod_ready — ждёт пока под поднимется (max 120s) wait_for_pod_ready() { local max_wait=120 local waited=0 echo " ⏳ Ожидание пода (max ${max_wait}s)..." while [[ $waited -lt $max_wait ]]; do local ready ready=$(kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/name=shared-sqs \ -o jsonpath='{.items[0].status.conditions[?(@.type=="Ready")].status}' 2>/dev/null) if [[ "$ready" == "True" ]]; then echo " ✅ Под готов (${waited}s)" return 0 fi sleep 2 waited=$((waited + 2)) done echo " ⚠️ Под не готов после ${max_wait}s" return 1 } # wait_for_healthy — ждёт пока сервис отвечает на /health wait_for_healthy() { local max_wait=120 local waited=0 echo " ⏳ Ожидание /health (max ${max_wait}s)..." while [[ $waited -lt $max_wait ]]; do local status status=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "${BASE_URL}/health" 2>/dev/null) if [[ "$status" == "200" ]]; then echo " ✅ Сервис отвечает (${waited}s)" return 0 fi sleep 2 waited=$((waited + 2)) done echo " ⚠️ Сервис не отвечает после ${max_wait}s" return 1 } cleanup() { rm -rf "$TMPDIR_STRESS" 2>/dev/null } trap cleanup EXIT echo "╔══════════════════════════════════════════════════════════════╗" echo "║ shared-sqs STRESS TEST — $(date '+%Y-%m-%d %H:%M:%S') ║" echo "║ Endpoint: ${BASE_URL} ║" echo "║ Workers: ${CONCURRENT_WORKERS} Msgs/worker: ${MESSAGES_PER_WORKER} Burst: ${BURST_SIZE} ║" echo "╚══════════════════════════════════════════════════════════════╝" echo "" # ═══════════════════════════════════════════════════════════ # 1. ПОДГОТОВКА — создаём тенантов и очереди # ═══════════════════════════════════════════════════════════ echo "═══ 1. Подготовка — тенанты и очереди ═══" # Создаём тенантов для теста declare -a TENANT_AK declare -a TENANT_SK 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]}" done # Очередь для 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") 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 "" # ═══════════════════════════════════════════════════════════ # 2. КОНКУРЕНТНАЯ ОТПРАВКА — N воркеров пишут параллельно # ═══════════════════════════════════════════════════════════ echo "═══ 2. Конкурентная отправка: ${CONCURRENT_WORKERS} воркеров × ${MESSAGES_PER_WORKER} сообщений ═══" TOTAL_MSGS=$((CONCURRENT_WORKERS * MESSAGES_PER_WORKER)) echo " Всего будет отправлено: $TOTAL_MSGS сообщений" # Запускаем воркеров параллельно 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}" \ --message-body "worker-${w}-msg-${m}-${TS}" 2>&1) if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)) else fail=$((fail + 1)) fi done echo "${ok}:${fail}" > "${TMPDIR_STRESS}/send_w${w}.result" ) & done echo " ⏳ Ожидание завершения ${CONCURRENT_WORKERS} воркеров..." wait # Подсчитываем результаты SEND_OK=0 SEND_FAIL=0 for w in $(seq 1 $CONCURRENT_WORKERS); do if [[ -f "${TMPDIR_STRESS}/send_w${w}.result" ]]; then IFS=':' read -r ok fail < "${TMPDIR_STRESS}/send_w${w}.result" SEND_OK=$((SEND_OK + ok)) SEND_FAIL=$((SEND_FAIL + fail)) fi done echo " Отправлено: ${SEND_OK} ok, ${SEND_FAIL} fail" check_eq "Все сообщения отправлены" "$SEND_OK" "$TOTAL_MSGS" # Проверяем через GetQueueAttributes — ApproximateNumberOfMessages sleep 2 R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" get-queue-attributes \ --queue-url "${BASE_URL}/queue/${STRESS_Q}" \ --attribute-names ApproximateNumberOfMessages 2>&1) APPROX=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+') echo " Очередь сообщает: ~${APPROX} сообщений" check_eq "Количество в очереди совпадает" "${APPROX:-0}" "$TOTAL_MSGS" echo "" # ═══════════════════════════════════════════════════════════ # 3. КОНКУРЕНТНОЕ ЧТЕНИЕ — гонка за сообщения # ═══════════════════════════════════════════════════════════ echo "═══ 3. Конкурентное чтение: ${CONCURRENT_WORKERS} воркеров забирают ═══" # Каждый воркер читает и удаляет for w in $(seq 1 $CONCURRENT_WORKERS); do ( received=0 deleted=0 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}" \ --max-number-of-messages 10 \ --wait-time-seconds 1 2>&1) # Считаем полученные MSG_COUNT=$(echo "$R" | grep -c '"MessageId"' || true) if [[ "$MSG_COUNT" -eq 0 ]]; then empty_rounds=$((empty_rounds + 1)) continue fi empty_rounds=0 received=$((received + MSG_COUNT)) # Удаляем каждое полученное 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}" \ --receipt-handle "$handle" 2>&1) deleted=$((deleted + 1)) done <<< "$HANDLES" done echo "${received}:${deleted}" > "${TMPDIR_STRESS}/recv_w${w}.result" ) & done echo " ⏳ Ожидание воркеров-читателей..." wait # Подсчёт RECV_TOTAL=0 DEL_TOTAL=0 for w in $(seq 1 $CONCURRENT_WORKERS); do if [[ -f "${TMPDIR_STRESS}/recv_w${w}.result" ]]; then IFS=':' read -r recv del < "${TMPDIR_STRESS}/recv_w${w}.result" RECV_TOTAL=$((RECV_TOTAL + recv)) DEL_TOTAL=$((DEL_TOTAL + del)) fi done echo " Прочитано: ${RECV_TOTAL}, удалено: ${DEL_TOTAL}" # Каждое сообщение может быть прочитано несколько раз (visibility timeout), # но удалённых не должно быть больше отправленных check_ge "Прочитано >= отправлено (с повторами ок)" "$RECV_TOTAL" "$TOTAL_MSGS" check_eq "Удалено = отправлено (нет дубликатов)" "$DEL_TOTAL" "$TOTAL_MSGS" # Очередь должна быть пуста sleep 2 R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" get-queue-attributes \ --queue-url "${BASE_URL}/queue/${STRESS_Q}" \ --attribute-names ApproximateNumberOfMessages 2>&1) APPROX=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+') check_eq "Очередь пуста после чтения" "${APPROX:-0}" "0" echo "" # ═══════════════════════════════════════════════════════════ # 4. MULTI-TENANT ИЗОЛЯЦИЯ ПОД НАГРУЗКОЙ # ═══════════════════════════════════════════════════════════ echo "═══ 4. Multi-tenant изоляция: ${TENANT_COUNT} тенантов, параллельная нагрузка ═══" MSGS_PER_TENANT=30 # Каждый тенант отправляет уникальные сообщения в одноимённую очередь for i in $(seq 1 $TENANT_COUNT); do ( 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}" \ --message-body "tenant-${i}-isolation-${m}" 2>&1) if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi done echo "$ok" > "${TMPDIR_STRESS}/tenant_send_${i}.result" ) & done wait # Каждый тенант читает — должен видеть ТОЛЬКО свои сообщения for i in $(seq 1 $TENANT_COUNT); do ( 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}" \ --max-number-of-messages 10 \ --wait-time-seconds 1 2>&1) MSG_COUNT=$(echo "$R" | grep -c '"Body"' || true) if [[ "$MSG_COUNT" -eq 0 ]]; then empty_rounds=$((empty_rounds + 1)) continue fi empty_rounds=0 # Проверяем что тело содержит tenant-{i} while IFS= read -r body; do if echo "$body" | grep -q "tenant-${i}-"; then own=$((own + 1)) else foreign=$((foreign + 1)) fi done < <(echo "$R" | grep -oP '"Body"\s*:\s*"\K[^"]+') # Удаляем 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}" \ --receipt-handle "$handle" >/dev/null 2>&1 done <<< "$HANDLES" done echo "${own}:${foreign}" > "${TMPDIR_STRESS}/tenant_recv_${i}.result" ) & done wait # Проверяем: чужих сообщений быть не должно ALL_FOREIGN=0 ALL_OWN=0 for i in $(seq 1 $TENANT_COUNT); do if [[ -f "${TMPDIR_STRESS}/tenant_recv_${i}.result" ]]; then IFS=':' read -r own foreign < "${TMPDIR_STRESS}/tenant_recv_${i}.result" echo " Тенант ${i}: своих=${own}, чужих=${foreign}" ALL_OWN=$((ALL_OWN + own)) ALL_FOREIGN=$((ALL_FOREIGN + foreign)) fi done check_eq "Чужих сообщений: 0 (tenant isolation)" "$ALL_FOREIGN" "0" check_ge "Свои сообщения получены" "$ALL_OWN" "$((TENANT_COUNT * MSGS_PER_TENANT))" echo "" # ═══════════════════════════════════════════════════════════ # 5. BURST — резкий всплеск запросов # ═══════════════════════════════════════════════════════════ 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}" \ --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" ) & done echo " ⏳ Ожидание burst..." wait BURST_OK=0 for b in $(seq 1 $BURST_SIZE); do if [[ -f "${TMPDIR_STRESS}/burst_${b}.result" ]]; then val=$(cat "${TMPDIR_STRESS}/burst_${b}.result") BURST_OK=$((BURST_OK + val)) fi done echo " Burst: ${BURST_OK}/${BURST_SIZE} успешно" 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}" \ --attribute-names ApproximateNumberOfMessages 2>&1) BURST_APPROX=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+') echo " Очередь burst: ~${BURST_APPROX} сообщений" check_eq "Burst: все в очереди" "${BURST_APPROX:-0}" "$BURST_OK" echo "" # ═══════════════════════════════════════════════════════════ # 6. KILL POD — рестарт и восстановление из Redis # ═══════════════════════════════════════════════════════════ echo "═══ 6. Kill pod — рестарт и восстановление из Redis ═══" # Перед киллом — запоминаем сколько сообщений в burst-очереди BEFORE_KILL="${BURST_APPROX:-0}" 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}" \ --message-body "pre-kill-${m}-${TS}" >/dev/null 2>&1 done EXPECTED_AFTER=$((BEFORE_KILL + EXTRA_MSGS)) echo " Отправлено ещё $EXTRA_MSGS сообщений. Ожидаем: ${EXPECTED_AFTER}" # Убиваем под echo " 🔪 Убиваю под..." POD_NAME=$(kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/name=shared-sqs \ -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) echo " Pod: $POD_NAME" kubectl delete pod "$POD_NAME" -n "$NAMESPACE" --grace-period=0 --force 2>&1 | head -2 echo " Под удалён, ждём рестарт..." # Ждём пока новый под поднимется wait_for_pod_ready 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}" \ --attribute-names ApproximateNumberOfMessages 2>&1) AFTER_KILL=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+') echo " Сообщений после рестарта: ${AFTER_KILL:-0}" check_eq "Данные восстановлены из Redis" "${AFTER_KILL:-0}" "$EXPECTED_AFTER" # Проверяем что тенанты тоже восстановились R=$(admin_api GET "/admin/tenants") TENANT_CHECK=$(echo "$R" | grep -c "stress-t1-${TS}" || true) check_ge "Тенанты восстановлены из Redis" "$TENANT_CHECK" "1" echo "" # ═══════════════════════════════════════════════════════════ # 7. REDIS DISCONNECT — сервис при потере Redis # ═══════════════════════════════════════════════════════════ echo "═══ 7. Redis disconnect simulation ═══" # Находим Redis endpoint из env пода NEW_POD=$(kubectl get pods -n "$NAMESPACE" -l app.kubernetes.io/name=shared-sqs \ -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) # Добавляем NetworkPolicy чтобы заблокировать трафик к Redis cat <<'NETPOL' | kubectl apply -n "$NAMESPACE" -f - 2>&1 apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: stress-test-block-redis spec: podSelector: matchLabels: app.kubernetes.io/name: shared-sqs policyTypes: - Egress egress: - to: - ipBlock: cidr: 0.0.0.0/0 except: - 10.0.0.0/8 ports: - protocol: TCP port: 443 - protocol: TCP port: 80 - to: - namespaceSelector: {} ports: - protocol: TCP port: 53 - protocol: UDP port: 53 NETPOL echo " NetworkPolicy: заблокирован egress к Redis (10.0.0.0/8 except DNS)" sleep 5 # Пробуем API — должен вернуть ошибку или graceful degradation, не 502 R_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 \ -H "Authorization: Bearer $ADMIN_TOKEN" \ "${BASE_URL}/admin/tenants" 2>/dev/null) echo " HTTP status при потере Redis: $R_CODE" TOTAL=$((TOTAL + 1)) # Сервис может вернуть 200 (из кеша) или 500/503 (graceful), но НЕ пустоту/таймаут if [[ "$R_CODE" =~ ^[2345][0-9][0-9]$ ]]; then PASS=$((PASS + 1)) echo " ✅ Сервис отвечает при потере Redis (HTTP $R_CODE)" else FAIL=$((FAIL + 1)) echo " ❌ Сервис не отвечает при потере Redis (HTTP $R_CODE)" fi # Восстанавливаем доступ к Redis — удаляем NetworkPolicy kubectl delete networkpolicy stress-test-block-redis -n "$NAMESPACE" 2>&1 | head -1 echo " NetworkPolicy удалена — Redis доступен снова" sleep 5 # Проверяем что после восстановления Redis сервис работает R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" send-message \ --queue-url "${BASE_URL}/queue/${BURST_Q}" \ --message-body "after-redis-recovery-${TS}" 2>&1) check "Сервис работает после восстановления Redis" "$R" "MessageId" echo "" # ═══════════════════════════════════════════════════════════ # 8. ПАРАЛЛЕЛЬНЫЕ ОПЕРАЦИИ НА ОДНОЙ ОЧЕРЕДИ (смешанная нагрузка) # ═══════════════════════════════════════════════════════════ echo "═══ 8. Смешанная нагрузка: send + receive + delete одновременно ═══" MIXED_Q="stress-mixed-${TS}" R=$(sqs "${TENANT_AK[1]}" "${TENANT_SK[1]}" create-queue --queue-name "$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}" \ --message-body "mixed-init-${m}" >/dev/null 2>&1 done echo " Начальная загрузка: 50 сообщений" # Параллельно: 5 отправителей + 5 читателей-удалителей + 2 GetQueueAttributes MIXED_DURATION=15 # секунды # Отправители 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}" \ --message-body "mixed-send-${w}-$(date +%s%N)" 2>&1) if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi done echo "$ok" > "${TMPDIR_STRESS}/mixed_send_${w}.result" ) & done # Читатели-удалители 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}" \ --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}" \ --receipt-handle "$handle" >/dev/null 2>&1 recv=$((recv + 1)) done <<< "$HANDLES" done echo "$recv" > "${TMPDIR_STRESS}/mixed_recv_${w}.result" ) & done # GetQueueAttributes (не должен падать при нагрузке) for w in $(seq 1 2); do ( end=$((SECONDS + MIXED_DURATION)) 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}" \ --attribute-names All 2>&1) if echo "$R" | grep -q "ApproximateNumberOfMessages"; then ok=$((ok + 1)) else fail=$((fail + 1)) fi sleep 0.5 done echo "${ok}:${fail}" > "${TMPDIR_STRESS}/mixed_attr_${w}.result" ) & done echo " ⏳ Смешанная нагрузка ${MIXED_DURATION}s..." wait # Подсчёт MIXED_SENT=0 MIXED_RECV=0 ATTR_OK=0 ATTR_FAIL=0 for w in $(seq 1 5); do [[ -f "${TMPDIR_STRESS}/mixed_send_${w}.result" ]] && MIXED_SENT=$((MIXED_SENT + $(cat "${TMPDIR_STRESS}/mixed_send_${w}.result"))) [[ -f "${TMPDIR_STRESS}/mixed_recv_${w}.result" ]] && MIXED_RECV=$((MIXED_RECV + $(cat "${TMPDIR_STRESS}/mixed_recv_${w}.result"))) done for w in $(seq 1 2); do if [[ -f "${TMPDIR_STRESS}/mixed_attr_${w}.result" ]]; then IFS=':' read -r ok fail < "${TMPDIR_STRESS}/mixed_attr_${w}.result" ATTR_OK=$((ATTR_OK + ok)) ATTR_FAIL=$((ATTR_FAIL + fail)) fi done echo " Отправлено: $MIXED_SENT, прочитано+удалено: $MIXED_RECV" echo " GetQueueAttributes: ok=$ATTR_OK, fail=$ATTR_FAIL" check_ge "Смешанная нагрузка: отправлено > 0" "$MIXED_SENT" "1" check_ge "Смешанная нагрузка: прочитано > 0" "$MIXED_RECV" "1" check_eq "GetQueueAttributes: ни одного сбоя" "$ATTR_FAIL" "0" echo "" # ═══════════════════════════════════════════════════════════ # 9. CLEANUP # ═══════════════════════════════════════════════════════════ 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 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 echo " Очереди и тенанты очищены" echo "" # ═══════════════════════════════════════════════════════════ # ИТОГИ # ═══════════════════════════════════════════════════════════ echo "╔══════════════════════════════════════════════════════════════╗" echo "║ ИТОГО: ${PASS}/${TOTAL} ✅ ${FAIL}/${TOTAL} ❌ ║" echo "╚══════════════════════════════════════════════════════════════╝" if [[ $FAIL -gt 0 ]]; then exit 1 fi