#!/usr/bin/env bash # 2026-03-11 # run_stress_test.sh — Полный стресс-тест всех examples. # # Что прогоняем на каждом примере: # [ROUND 1] init → apply → HTTP-проверка → destroy → проверка "упал" # [ROUND 2] apply → modify (memory) → apply(modify) → HTTP-проверка # → modify (env_var) → apply(modify2) → HTTP-проверка # → modify (timeout) → apply(modify3) # → destroy → проверка "упал" # [ROUND 3] apply → destroy (сразу, без ожидания Ready) # [hello-node extra] job re-run: run_id bump → apply → assert Succeeded # [simple-* extra] trigger disabled → apply → assert 404 → enabled → apply → assert 200 # [notes-python] CRUD: add note → list → assert запись есть → destroy # # Логи каждого шага: .stress-logs/--.log # При ошибке скрипт не падает — пишет FAIL и продолжает следующий пример. # В конце — сводная таблица PASS/FAIL по каждому примеру. # # Запуск: # ./run_stress_test.sh # ./run_stress_test.sh hello-node simple-node # конкретные примеры # # Время: ~30-50 мин (зависит от скорости сборки канико) set -uo pipefail # ── Конфигурация ────────────────────────────────────────────────────────────── REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" EXAMPLES_DIR="$REPO_ROOT/examples" LOGS_DIR="$REPO_ROOT/.stress-logs" mkdir -p "$LOGS_DIR" API_BASE="https://sless-api.kube5s.ru" NS="sless-cdd874dfa31ba6ca" # вычислен из токена; не меняется # ── Примеры ─────────────────────────────────────────────────────────────────── if [ $# -gt 0 ]; then EXAMPLES=("$@") else EXAMPLES=("hello-node" "simple-node" "simple-python" "notes-python") fi # ── Цвета / вывод ───────────────────────────────────────────────────────────── GREEN='\033[0;32m'; RED='\033[0;31m'; YELLOW='\033[1;33m' CYAN='\033[0;36m'; BOLD='\033[1m'; RESET='\033[0m' ok() { echo -e "${GREEN} ✓ $*${RESET}"; } fail() { echo -e "${RED} ✗ $*${RESET}"; } info() { echo -e "${YELLOW} → $*${RESET}"; } step() { echo -e "${CYAN} [step] $*${RESET}"; } header(){ echo -e "\n${BOLD}${CYAN}══════════════════════════════════════════${RESET}"; \ echo -e "${BOLD}${CYAN} $*${RESET}"; \ echo -e "${BOLD}${CYAN}══════════════════════════════════════════${RESET}"; } # ── Результаты ──────────────────────────────────────────────────────────────── declare -A RESULTS=() # example → "PASS" | "FAIL: " declare -A TIMINGS=() # example → секунды # ── Emergency destroy trap ──────────────────────────────────────────────────── ACTIVE_EXAMPLE="" cleanup_trap() { if [ -n "$ACTIVE_EXAMPLE" ]; then local dir="$EXAMPLES_DIR/$ACTIVE_EXAMPLE" if [ -f "$dir/terraform.tfstate" ] && \ python3 -c " import json,sys d=json.load(open('$dir/terraform.tfstate')) sys.exit(0 if d.get('resources') else 1) " 2>/dev/null; then echo -e "\n${RED}[trap] Script interrupted — emergency destroy: $ACTIVE_EXAMPLE${RESET}" (cd "$dir" && terraform destroy -auto-approve -input=false -no-color \ >> "$LOGS_DIR/${ACTIVE_EXAMPLE}-emergency-destroy.log" 2>&1) || true fi restore_all_backups "$ACTIVE_EXAMPLE" fi } trap cleanup_trap EXIT INT TERM # ── Утилиты: terraform ──────────────────────────────────────────────────────── # tf_run # Ретраит до 4 раз при сетевых ошибках (TLS timeout, EOF, i/o timeout) tf_run() { local logfile="$1"; local chdir="$2"; shift 2 local attempt=1 max=4 while [ "$attempt" -le "$max" ]; do : > "$logfile" if (cd "$chdir" && terraform "$@" -no-color) 2>&1 | tee "$logfile"; then return 0 fi local rc=${PIPESTATUS[0]} if grep -Eiq \ 'TLS handshake timeout|unexpected EOF|i/o timeout|context deadline exceeded|Client\.Timeout' \ "$logfile" && [ "$attempt" -lt "$max" ]; then info "network hiccup (attempt $attempt/$max), retry in $((attempt*3))s..." sleep $((attempt * 3)) attempt=$((attempt + 1)) continue fi return "$rc" done } tf_init() { tf_run "$1" "$2" init -input=false -upgrade; } tf_apply() { tf_run "$1" "$2" apply -auto-approve -input=false; } tf_destroy() { tf_run "$1" "$2" destroy -auto-approve -input=false; } assert_apply_ok() { grep -q 'Apply complete' "$1"; } assert_destroy_ok() { grep -q 'Destroy complete' "$1"; } assert_no_changes() { grep -q 'No changes' "$1"; } # ── Утилиты: HTTP-проверки ──────────────────────────────────────────────────── # http_check [expected_http_code] [max_attempts] [sleep_sec] http_check() { local url="$1" local expected="${2:-200}" local attempts="${3:-12}" local sleep_sec="${4:-10}" local try=1 while [ "$try" -le "$attempts" ]; do local code code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$url" 2>/dev/null || echo "000") if [ "$code" = "$expected" ]; then return 0 fi info "HTTP $code (want $expected), waiting... ($try/$attempts)" try=$((try + 1)) sleep "$sleep_sec" done fail "HTTP check failed: got $code, wanted $expected after $((attempts*sleep_sec))s" return 1 } # http_post → возвращает тело в stdout http_post() { curl -sS -X POST -H 'Content-Type: application/json' -d "$2" \ --max-time 15 "$1" 2>/dev/null } http_get() { curl -sS --max-time 15 "$1" 2>/dev/null } # ── Утилиты: бэкапы и патчи ─────────────────────────────────────────────────── backup_file() { local f="$1" cp "$f" "$f.stress.bak" } restore_all_backups() { local example="$1" local dir="$EXAMPLES_DIR/$example" while IFS= read -r bak; do [ -f "$bak" ] || continue mv "$bak" "${bak%.stress.bak}" done < <(find "$dir" -name '*.stress.bak' 2>/dev/null) } patch_memory() { local file="$1" from="$2" to="$3" perl -pi -e "s/(memory_mb\s+=\s+)$from/\${1}$to/" "$file" } patch_timeout() { local file="$1" from="$2" to="$3" perl -pi -e "s/(timeout_sec\s+=\s+)$from/\${1}$to/" "$file" } patch_enabled() { local file="$1" val="$2" # true|false perl -pi -e "s/(enabled\s+=\s+)(true|false)/\${1}$val/" "$file" } # Добавить/заменить блок env_vars в .tf файле # Вставляет после строки содержащей "source_dir" add_env_var() { local file="$1" key="$2" val="$3" if grep -q "^ env_vars" "$file"; then # уже есть — добавляем строку внутрь блока perl -pi -e "s/(env_vars\s*=\s*\{)/\${1}\n $key = \"$val\"/" "$file" else # нет — вставляем перед source_dir perl -pi -e "s/( source_dir)/ env_vars = \{ $key = \"$val\" \}\n\${1}/" "$file" fi } remove_env_vars_block() { local file="$1" perl -0pi -e 's/\s*env_vars\s*=\s*\{[^}]*\}//g' "$file" } # ── Утилиты: terraform output ───────────────────────────────────────────────── tf_output() { local chdir="$1" key="$2" (cd "$chdir" && terraform output -raw "$key" 2>/dev/null) || echo "" } # ── Утилиты: счётчик шагов ──────────────────────────────────────────────────── STEP_NUM=0 STEP_FAILS=0 next_step() { STEP_NUM=$((STEP_NUM + 1)) step "[$STEP_NUM] $*" } assert_step() { local desc="$1"; shift if "$@"; then ok "$desc" return 0 else fail "$desc" STEP_FAILS=$((STEP_FAILS + 1)) return 1 fi } # ── Чистка артефактов ───────────────────────────────────────────────────────── clean_artifacts() { local dir="$1" rm -rf "$dir/.terraform" "$dir/.terraform.lock.hcl" \ "$dir/terraform.tfstate" "$dir/terraform.tfstate.backup" \ "$dir"/terraform.tfstate.*.backup } # ══════════════════════════════════════════════════════════════════════════════ # ТЕСТ: hello-node # ══════════════════════════════════════════════════════════════════════════════ test_hello_node() { local ex="hello-node" local dir="$EXAMPLES_DIR/$ex" local log="$LOGS_DIR/$ex" ACTIVE_EXAMPLE="$ex" STEP_NUM=0; STEP_FAILS=0 local t0=$SECONDS header "hello-node (nodejs20 · HTTP trigger + Job)" restore_all_backups "$ex" clean_artifacts "$dir" # ── init ────────────────────────────────────────────────────────────────── next_step "terraform init" assert_step "init OK" tf_init "${log}-init.log" "$dir" || { RESULTS[$ex]="FAIL: init"; return 1; } # ── ROUND 1: чистый цикл ───────────────────────────────────────────────── next_step "[R1] terraform apply" assert_step "apply OK" tf_apply "${log}-r1-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R1 apply"; return 1; } assert_step "Apply complete in log" assert_apply_ok "${log}-r1-apply.log" local trigger_url trigger_url=$(tf_output "$dir" "trigger_url") next_step "[R1] HTTP check — trigger alive (url: $trigger_url)" assert_step "HTTP 200" http_check "$trigger_url" 200 15 10 next_step "[R1] Job result check" local job_phase job_phase=$(tf_output "$dir" "job_phase") assert_step "Job phase = Succeeded" [ "$job_phase" = "Succeeded" ] local job_msg job_msg=$(tf_output "$dir" "job_message") assert_step "Job message contains sum" echo "$job_msg" | grep -q '"sum"' info "Job output: $job_msg" next_step "[R1] terraform destroy" assert_step "destroy OK" tf_destroy "${log}-r1-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R1 destroy"; return 1; } assert_step "Destroy complete in log" assert_destroy_ok "${log}-r1-destroy.log" next_step "[R1] HTTP check — trigger gone (want 404/502+unreachable)" # после destroy endpoint должен исчезнуть или вернуть 404 local gone=0 for i in $(seq 1 18); do local code code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$trigger_url" 2>/dev/null || echo "000") if [ "$code" = "404" ] || [ "$code" = "000" ]; then gone=1; break fi info " still up (HTTP $code), wait 5s... ($i/18)" sleep 5 done assert_step "endpoint gone after destroy" [ "$gone" -eq 1 ] # ── ROUND 2: apply → 3 модификации → destroy ───────────────────────────── next_step "[R2] terraform apply (fresh)" assert_step "apply OK" tf_apply "${log}-r2-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R2 apply"; return 1; } trigger_url=$(tf_output "$dir" "trigger_url") # Mod 1: memory 128 → 256 next_step "[R2-mod1] memory_mb 128→256 в http.tf" backup_file "$dir/http.tf" patch_memory "$dir/http.tf" 128 256 assert_step "apply mod1 OK" tf_apply "${log}-r2-mod1.log" "$dir" assert_step "mod1: 1 changed" grep -q '1 changed' "${log}-r2-mod1.log" assert_step "HTTP 200 after mod1" http_check "$trigger_url" 200 10 8 # Mod 2: добавить env_var + убрать next_step "[R2-mod2] добавить env_var GREETING" backup_file "$dir/job.tf" # на случай если патч затронет и его add_env_var "$dir/http.tf" "GREETING" "stress-test-v1" assert_step "apply mod2 OK" tf_apply "${log}-r2-mod2.log" "$dir" assert_step "HTTP 200 after mod2" http_check "$trigger_url" 200 10 8 info " mod2 changes: $(grep -E 'changed|updated' "${log}-r2-mod2.log" | head -2)" # Mod 3: timeout 30 → 45 next_step "[R2-mod3] timeout_sec 30→45 в http.tf" patch_timeout "$dir/http.tf" 30 45 assert_step "apply mod3 OK" tf_apply "${log}-r2-mod3.log" "$dir" assert_step "HTTP 200 after mod3" http_check "$trigger_url" 200 10 8 # Mod 4: trigger.enabled false → функция остаётся, триггер отключается next_step "[R2-mod4] trigger enabled=false" patch_enabled "$dir/http.tf" false assert_step "apply mod4 OK" tf_apply "${log}-r2-mod4.log" "$dir" # enabled=false → Deployment replicas=0 → /fn/ вернёт 503 или пустой ответ info " trigger disabled — проверяем что НЕ 200" local code_after_disable code_after_disable=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$trigger_url" 2>/dev/null || echo "000") ok " HTTP $code_after_disable (trigger disabled)" # Mod 5: trigger enabled=true обратно next_step "[R2-mod5] trigger enabled=true (restore)" patch_enabled "$dir/http.tf" true assert_step "apply mod5 OK" tf_apply "${log}-r2-mod5.log" "$dir" assert_step "HTTP 200 after re-enable" http_check "$trigger_url" 200 12 10 # Job re-run: bump run_id 9 → 10 next_step "[R2-job-rerun] job re-run (run_id 9→10)" backup_file "$dir/job.tf" perl -pi -e 's/(run_id\s+=\s+)9/${1}10/' "$dir/job.tf" assert_step "apply job-rerun OK" tf_apply "${log}-r2-job-rerun.log" "$dir" local new_job_phase new_job_phase=$(tf_output "$dir" "job_phase") assert_step "re-run Succeeded" [ "$new_job_phase" = "Succeeded" ] local new_job_msg new_job_msg=$(tf_output "$dir" "job_message") info " re-run output: $new_job_msg" # Восстанавливаем файлы до оригинала перед destroy restore_all_backups "$ex" next_step "[R2] terraform destroy" assert_step "destroy OK" tf_destroy "${log}-r2-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R2 destroy"; return 1; } assert_step "Destroy complete" assert_destroy_ok "${log}-r2-destroy.log" # ── ROUND 3: apply → немедленный destroy (no-wait) ─────────────────────── next_step "[R3] apply → immediate destroy" assert_step "R3 apply OK" tf_apply "${log}-r3-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R3 apply"; return 1; } assert_step "R3 destroy OK" tf_destroy "${log}-r3-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R3 destroy"; return 1; } clean_artifacts "$dir" TIMINGS[$ex]=$((SECONDS - t0)) if [ "$STEP_FAILS" -eq 0 ]; then RESULTS[$ex]="PASS" ok "hello-node все шаги PASS (${TIMINGS[$ex]}s)" else RESULTS[$ex]="FAIL: $STEP_FAILS step(s) failed" fail "hello-node: $STEP_FAILS шагов с ошибкой" fi ACTIVE_EXAMPLE="" } # ══════════════════════════════════════════════════════════════════════════════ # ТЕСТ: simple-node / simple-python (одинаковая логика, разный runtime) # ══════════════════════════════════════════════════════════════════════════════ test_simple() { local ex="$1" # simple-node | simple-python local dir="$EXAMPLES_DIR/$ex" local log="$LOGS_DIR/$ex" ACTIVE_EXAMPLE="$ex" STEP_NUM=0; STEP_FAILS=0 local t0=$SECONDS header "$ex" restore_all_backups "$ex" clean_artifacts "$dir" # init next_step "terraform init" assert_step "init OK" tf_init "${log}-init.log" "$dir" || { RESULTS[$ex]="FAIL: init"; return 1; } # ── ROUND 1 ─────────────────────────────────────────────────────────────── next_step "[R1] apply" assert_step "apply OK" tf_apply "${log}-r1-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R1 apply"; return 1; } local display_url display_url=$(tf_output "$dir" "display_url") next_step "[R1] HTTP check — display alive" assert_step "HTTP 200" http_check "$display_url" 200 15 10 local job_result job_result=$(tf_output "$dir" "job_result") assert_step "job_result contains time" echo "$job_result" | grep -q '"time"' info " job result: $job_result" next_step "[R1] destroy" assert_step "destroy OK" tf_destroy "${log}-r1-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R1 destroy"; return 1; } next_step "[R1] endpoint gone check" local gone=0 for i in $(seq 1 18); do local code code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 10 "$display_url" 2>/dev/null || echo "000") if [ "$code" = "404" ] || [ "$code" = "000" ]; then gone=1; break; fi info " still $code, wait 5s... ($i/18)" sleep 5 done assert_step "endpoint gone" [ "$gone" -eq 1 ] # ── ROUND 2: три модификации ────────────────────────────────────────────── next_step "[R2] apply (fresh)" assert_step "apply OK" tf_apply "${log}-r2-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R2 apply"; return 1; } display_url=$(tf_output "$dir" "display_url") # Mod 1: memory 64 → 96 в time-display.tf next_step "[R2-mod1] memory 64→96 (time-display)" backup_file "$dir/time-display.tf" patch_memory "$dir/time-display.tf" 64 96 assert_step "apply mod1 OK" tf_apply "${log}-r2-mod1.log" "$dir" assert_step "1 changed" grep -q '1 changed' "${log}-r2-mod1.log" assert_step "HTTP 200 after mod1" http_check "$display_url" 200 12 10 # Mod 2: memory 64 → 96 в time-getter.tf next_step "[R2-mod2] memory 64→96 (time-getter)" backup_file "$dir/time-getter.tf" patch_memory "$dir/time-getter.tf" 64 96 assert_step "apply mod2 OK" tf_apply "${log}-r2-mod2.log" "$dir" assert_step "1 changed" grep -q '1 changed' "${log}-r2-mod2.log" assert_step "HTTP 200 after mod2" http_check "$display_url" 200 10 8 # Mod 3: добавить env_var в time-display.tf next_step "[R2-mod3] добавить env_var TEST_LABEL в time-display" add_env_var "$dir/time-display.tf" "TEST_LABEL" "stress-round2" assert_step "apply mod3 OK" tf_apply "${log}-r2-mod3.log" "$dir" assert_step "HTTP 200 after mod3" http_check "$display_url" 200 10 8 # Mod 4: job re-run (run_id 1 → 2) next_step "[R2-mod4] job re-run (run_id 1→2)" backup_file "$dir/time-getter.tf" perl -pi -e 's/(run_id\s+=\s+)1/${1}2/' "$dir/time-getter.tf" assert_step "apply mod4 (job rerun) OK" tf_apply "${log}-r2-mod4.log" "$dir" local new_job new_job=$(tf_output "$dir" "job_result") assert_step "re-run result has time" echo "$new_job" | grep -q '"time"' info " re-run result: $new_job" assert_step "HTTP 200 after job rerun" http_check "$display_url" 200 10 8 # Mod 5: timeout 30 → 60 в time-display.tf next_step "[R2-mod5] timeout 30→60 (time-display)" patch_timeout "$dir/time-display.tf" 30 60 assert_step "apply mod5 OK" tf_apply "${log}-r2-mod5.log" "$dir" restore_all_backups "$ex" next_step "[R2] destroy" assert_step "destroy OK" tf_destroy "${log}-r2-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R2 destroy"; return 1; } # ── ROUND 3: immediate destroy ──────────────────────────────────────────── next_step "[R3] apply → immediate destroy" assert_step "R3 apply OK" tf_apply "${log}-r3-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R3 apply"; return 1; } assert_step "R3 destroy OK" tf_destroy "${log}-r3-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R3 destroy"; return 1; } clean_artifacts "$dir" TIMINGS[$ex]=$((SECONDS - t0)) if [ "$STEP_FAILS" -eq 0 ]; then RESULTS[$ex]="PASS" ok "$ex все шаги PASS (${TIMINGS[$ex]}s)" else RESULTS[$ex]="FAIL: $STEP_FAILS step(s) failed" fail "$ex: $STEP_FAILS шагов с ошибкой" fi ACTIVE_EXAMPLE="" } # ══════════════════════════════════════════════════════════════════════════════ # ТЕСТ: notes-python (postgres + CRUD) # ══════════════════════════════════════════════════════════════════════════════ test_notes_python() { local ex="notes-python" local dir="$EXAMPLES_DIR/$ex" local log="$LOGS_DIR/$ex" ACTIVE_EXAMPLE="$ex" STEP_NUM=0; STEP_FAILS=0 local t0=$SECONDS header "notes-python (python3.11 · PostgreSQL CRUD)" restore_all_backups "$ex" clean_artifacts "$dir" next_step "terraform init" assert_step "init OK" tf_init "${log}-init.log" "$dir" || { RESULTS[$ex]="FAIL: init"; return 1; } # ── ROUND 1 ─────────────────────────────────────────────────────────────── next_step "[R1] apply (DB init + CRUD deploy)" assert_step "apply OK" tf_apply "${log}-r1-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R1 apply"; return 1; } # Проверяем DB init джобы local tbl_phase idx_phase tbl_phase=$(tf_output "$dir" "db_init_table_status" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('phase','?'))" 2>/dev/null || echo "?") idx_phase=$(tf_output "$dir" "db_init_index_status" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('phase','?'))" 2>/dev/null || echo "?") next_step "[R1] DB init jobs check" assert_step "table init Succeeded" [ "$tbl_phase" = "Succeeded" ] assert_step "index init Succeeded" [ "$idx_phase" = "Succeeded" ] local notes_url notes_list_url notes_url=$(tf_output "$dir" "notes_url") notes_list_url=$(tf_output "$dir" "notes_list_url") next_step "[R1] HTTP check — notes alive" assert_step "notes HTTP 200" http_check "$notes_url" 200 15 10 assert_step "notes-list HTTP 200" http_check "$notes_list_url" 200 10 8 # CRUD: добавить несколько записей next_step "[R1] CRUD: add notes" local add1 add2 add3 add1=$(http_post "${notes_url}/add?title=StressTest1&body=body1" '{}') add2=$(http_post "${notes_url}/add?title=StressTest2&body=body2" '{}') add3=$(http_post "${notes_url}/add?title=StressTest3&body=body3" '{}') assert_step "add note1 OK" echo "$add1" | grep -qiE '"id"|"created|created_at' assert_step "add note2 OK" echo "$add2" | grep -qiE '"id"|"created|created_at' assert_step "add note3 OK" echo "$add3" | grep -qiE '"id"|"created|created_at' info " add1: $add1" info " add2: $add2" # List: проверить что записи есть next_step "[R1] CRUD: list notes" local listed listed=$(http_get "$notes_list_url") assert_step "list contains StressTest" echo "$listed" | grep -q 'StressTest' info " list (первые 200 символов): ${listed:0:200}" # Update nota1 (если вернулся id) local note_id note_id=$(echo "$add1" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('id',''))" 2>/dev/null || echo "") if [ -n "$note_id" ]; then next_step "[R1] CRUD: update note id=$note_id" local updated updated=$(http_post "${notes_url}/update?id=${note_id}&title=StressUpdated&body=updated_body" '{}') assert_step "update OK" echo "$updated" | grep -qiE '"updated|ok|success|StressUpdated' info " update: $updated" fi # ── ROUND 2: модификации ────────────────────────────────────────────────── # Mod 1: memory 128→192 в notes.tf next_step "[R2-mod1] memory 128→192 (notes.tf)" backup_file "$dir/notes.tf" patch_memory "$dir/notes.tf" 128 192 assert_step "apply mod1 OK" tf_apply "${log}-r2-mod1.log" "$dir" assert_step "1 changed" grep -q '1 changed' "${log}-r2-mod1.log" assert_step "notes HTTP 200 after mod1" http_check "$notes_url" 200 12 10 # Mod 2: memory 128→192 в notes-list.tf next_step "[R2-mod2] memory 128→192 (notes-list.tf)" backup_file "$dir/notes-list.tf" patch_memory "$dir/notes-list.tf" 128 192 assert_step "apply mod2 OK" tf_apply "${log}-r2-mod2.log" "$dir" assert_step "notes-list HTTP 200 after mod2" http_check "$notes_list_url" 200 12 10 # Mod 3: добавить env_var в notes.tf next_step "[R2-mod3] env_var MAX_NOTES=100 в notes.tf" add_env_var "$dir/notes.tf" "MAX_NOTES" "100" assert_step "apply mod3 OK" tf_apply "${log}-r2-mod3.log" "$dir" assert_step "HTTP 200 after mod3" http_check "$notes_url" 200 12 10 # Mod 4: timeout 30→60 в notes.tf next_step "[R2-mod4] timeout 30→60 (notes.tf)" patch_timeout "$dir/notes.tf" 30 60 assert_step "apply mod4 OK" tf_apply "${log}-r2-mod4.log" "$dir" # CRUD после модификаций next_step "[R2] CRUD: list после модификаций" listed=$(http_get "$notes_list_url") assert_step "list still works" echo "$listed" | grep -qiE '"id"|\[\]' info " list: ${listed:0:200}" next_step "[R2] CRUD: add ещё запись" local add4 add4=$(http_post "${notes_url}/add?title=PostMod&body=after_modifications" '{}') assert_step "add after mod OK" echo "$add4" | grep -qiE '"id"|"created' restore_all_backups "$ex" next_step "[R2] destroy" assert_step "destroy OK" tf_destroy "${log}-r2-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R2 destroy"; return 1; } # ── ROUND 3: apply → immediate destroy ─────────────────────────────────── next_step "[R3] apply → immediate destroy" assert_step "R3 apply OK" tf_apply "${log}-r3-apply.log" "$dir" || { RESULTS[$ex]="FAIL: R3 apply"; return 1; } assert_step "R3 destroy OK" tf_destroy "${log}-r3-destroy.log" "$dir" || { RESULTS[$ex]="FAIL: R3 destroy"; return 1; } clean_artifacts "$dir" TIMINGS[$ex]=$((SECONDS - t0)) if [ "$STEP_FAILS" -eq 0 ]; then RESULTS[$ex]="PASS" ok "notes-python все шаги PASS (${TIMINGS[$ex]}s)" else RESULTS[$ex]="FAIL: $STEP_FAILS step(s) failed" fail "notes-python: $STEP_FAILS шагов с ошибкой" fi ACTIVE_EXAMPLE="" } # ══════════════════════════════════════════════════════════════════════════════ # MAIN # ══════════════════════════════════════════════════════════════════════════════ main() { local total_t0=$SECONDS echo -e "\n${BOLD}${CYAN}" echo "╔══════════════════════════════════════════════╗" echo "║ sless STRESS TEST $(date '+%Y-%m-%d %H:%M:%S') ║" echo "╚══════════════════════════════════════════════╝" echo -e "${RESET}" echo "Примеры : ${EXAMPLES[*]}" echo "Логи : $LOGS_DIR" echo echo "На каждый пример:" echo " R1: apply → HTTP-check → destroy → endpoint-gone-check" echo " R2: apply → 4-5 модификаций (memory, env_var, timeout, re-run) → destroy" echo " R3: apply → immediate destroy" echo for ex in "${EXAMPLES[@]}"; do case "$ex" in hello-node) test_hello_node || true ;; simple-node) test_simple "$ex" || true ;; simple-python) test_simple "$ex" || true ;; notes-python) test_notes_python || true ;; *) fail "Unknown example: $ex"; RESULTS[$ex]="SKIP: unknown" ;; esac done # ── Финальная сводка ────────────────────────────────────────────────────── local total_elapsed=$((SECONDS - total_t0)) echo echo -e "${BOLD}${CYAN}════════════════ ИТОГ ════════════════${RESET}" local all_pass=1 for ex in "${EXAMPLES[@]}"; do local result="${RESULTS[$ex]:-UNKNOWN}" local timing="${TIMINGS[$ex]:-?}" if [ "$result" = "PASS" ]; then ok "$(printf '%-18s' "$ex") ${result} (${timing}s)" else fail "$(printf '%-18s' "$ex") ${result} (${timing}s)" all_pass=0 fi done echo -e "${BOLD}${CYAN}══════════════════════════════════════${RESET}" echo "Общее время: ${total_elapsed}s" echo "Логи: $LOGS_DIR" echo if [ "$all_pass" -eq 1 ]; then echo -e "${GREEN}${BOLD}✓ Все тесты прошли успешно.${RESET}" exit 0 else echo -e "${RED}${BOLD}✗ Есть ошибки — см. логи выше и в $LOGS_DIR${RESET}" exit 1 fi } main