diff --git a/doc/decisions/LEGACY.md b/doc/decisions/LEGACY.md deleted file mode 100644 index 276d3f7..0000000 --- a/doc/decisions/LEGACY.md +++ /dev/null @@ -1,5 +0,0 @@ -# ⚠️ ЛЕГАСИ — НЕ ИСПОЛЬЗОВАТЬ - -Исторические решения этапа разработки (апрель 2026). -**ЭТИМ НЕ РУКОВОДСТВОВАТЬСЯ.** Только для истории. -Помечено: 2026-08-13. diff --git a/doc/errors/LEGACY.md b/doc/errors/LEGACY.md deleted file mode 100644 index a166571..0000000 --- a/doc/errors/LEGACY.md +++ /dev/null @@ -1,5 +0,0 @@ -# ⚠️ ЛЕГАСИ — НЕ ИСПОЛЬЗОВАТЬ - -Исторические инциденты/ошибки этапа разработки. -**ЭТИМ НЕ РУКОВОДСТВОВАТЬСЯ.** Только для истории. -Помечено: 2026-08-13. diff --git a/doc/api/benchmark-comparison-2026-04-12.md b/doc/legacy/api/benchmark-comparison-2026-04-12.md similarity index 100% rename from doc/api/benchmark-comparison-2026-04-12.md rename to doc/legacy/api/benchmark-comparison-2026-04-12.md diff --git a/doc/api/yandex-message-queue-api-reference.md b/doc/legacy/api/yandex-message-queue-api-reference.md similarity index 100% rename from doc/api/yandex-message-queue-api-reference.md rename to doc/legacy/api/yandex-message-queue-api-reference.md diff --git a/doc/decisions/compatibility-2026-04-10.md b/doc/legacy/decisions/compatibility-2026-04-10.md similarity index 100% rename from doc/decisions/compatibility-2026-04-10.md rename to doc/legacy/decisions/compatibility-2026-04-10.md diff --git a/doc/decisions/demo-ui-showcase-2026-04-12.md b/doc/legacy/decisions/demo-ui-showcase-2026-04-12.md similarity index 100% rename from doc/decisions/demo-ui-showcase-2026-04-12.md rename to doc/legacy/decisions/demo-ui-showcase-2026-04-12.md diff --git a/doc/decisions/message-size-limit-2026-04-11.md b/doc/legacy/decisions/message-size-limit-2026-04-11.md similarity index 100% rename from doc/decisions/message-size-limit-2026-04-11.md rename to doc/legacy/decisions/message-size-limit-2026-04-11.md diff --git a/doc/decisions/resource-limits-plan.md b/doc/legacy/decisions/resource-limits-plan.md similarity index 100% rename from doc/decisions/resource-limits-plan.md rename to doc/legacy/decisions/resource-limits-plan.md diff --git a/doc/decisions/stress-testing-2026-04-11.md b/doc/legacy/decisions/stress-testing-2026-04-11.md similarity index 100% rename from doc/decisions/stress-testing-2026-04-11.md rename to doc/legacy/decisions/stress-testing-2026-04-11.md diff --git a/doc/errors/65kb-payload-timeout-2026-04-11.md b/doc/legacy/errors/65kb-payload-timeout-2026-04-11.md similarity index 100% rename from doc/errors/65kb-payload-timeout-2026-04-11.md rename to doc/legacy/errors/65kb-payload-timeout-2026-04-11.md diff --git a/doc/errors/compatibility-log-2026-04-10.md b/doc/legacy/errors/compatibility-log-2026-04-10.md similarity index 100% rename from doc/errors/compatibility-log-2026-04-10.md rename to doc/legacy/errors/compatibility-log-2026-04-10.md diff --git a/doc/errors/helm-upgrade-conflict-2026-04-12.md b/doc/legacy/errors/helm-upgrade-conflict-2026-04-12.md similarity index 100% rename from doc/errors/helm-upgrade-conflict-2026-04-12.md rename to doc/legacy/errors/helm-upgrade-conflict-2026-04-12.md diff --git a/doc/progress.md b/doc/legacy/progress.md similarity index 100% rename from doc/progress.md rename to doc/legacy/progress.md diff --git a/doc/thinking/2026-04-10.md b/doc/legacy/thinking/2026-04-10.md similarity index 100% rename from doc/thinking/2026-04-10.md rename to doc/legacy/thinking/2026-04-10.md diff --git a/doc/thinking/2026-04-11.md b/doc/legacy/thinking/2026-04-11.md similarity index 100% rename from doc/thinking/2026-04-11.md rename to doc/legacy/thinking/2026-04-11.md diff --git a/doc/thinking/2026-04-12.md b/doc/legacy/thinking/2026-04-12.md similarity index 100% rename from doc/thinking/2026-04-12.md rename to doc/legacy/thinking/2026-04-12.md diff --git a/doc/thinking/2026-04-13.md b/doc/legacy/thinking/2026-04-13.md similarity index 100% rename from doc/thinking/2026-04-13.md rename to doc/legacy/thinking/2026-04-13.md diff --git a/doc/thinking/LEGACY.md b/doc/thinking/LEGACY.md deleted file mode 100644 index 241ba79..0000000 --- a/doc/thinking/LEGACY.md +++ /dev/null @@ -1,5 +0,0 @@ -# ⚠️ ЛЕГАСИ — НЕ ИСПОЛЬЗОВАТЬ - -Исторические размышления/заметки этапа разработки. -**ЭТИМ НЕ РУКОВОДСТВОВАТЬСЯ.** Только для истории. -Помечено: 2026-08-13. diff --git a/tests/LEGACY.md b/tests/LEGACY.md deleted file mode 100644 index 6b4118f..0000000 --- a/tests/LEGACY.md +++ /dev/null @@ -1,5 +0,0 @@ -# ⚠️ ЛЕГАСИ — НЕ ИСПОЛЬЗОВАТЬ - -Старые тестовые скрипты (этап k8s-деплоя, домен qu.kube5s.ru). -**ЭТИМ НЕ РУКОВОДСТВОВАТЬСЯ.** Только для истории. -Помечено: 2026-08-13. diff --git a/tests/benchmark_compare_32k.sh b/tests/benchmark_compare_32k.sh deleted file mode 100755 index 0f3a234..0000000 --- a/tests/benchmark_compare_32k.sh +++ /dev/null @@ -1,380 +0,0 @@ -#!/bin/bash -# Updated: 2026-04-12 00:00 UTC -# benchmark_compare_32k.sh — сравнительный benchmark Yandex MQ vs shared-sqs только до 32KB. - -set -uo pipefail - -# ysqs выполняет aws sqs для Yandex MQ. -ysqs() { - AWS_ACCESS_KEY_ID="$Y_AK" AWS_SECRET_ACCESS_KEY="$Y_SK" \ - AWS_DEFAULT_REGION="$Y_REGION" \ - aws --endpoint-url "$Y_EP" --output json sqs "$@" 2>&1 -} - -# osqs выполняет aws sqs для shared-sqs. -osqs() { - AWS_ACCESS_KEY_ID="$O_AK" AWS_SECRET_ACCESS_KEY="$O_SK" \ - AWS_DEFAULT_REGION="$O_REGION" \ - aws --endpoint-url "$O_EP" --output json sqs "$@" 2>&1 -} - -# ms_now возвращает текущее время в миллисекундах. -ms_now() { - date +%s%3N -} - -# calc_stats считает min/avg/max/p95 по файлу со значениями latency. -calc_stats() { - local file_path="$1" - sort -n "$file_path" | awk ' - { values[NR] = $1; total += $1 } - END { - count = NR - if (count == 0) { - print "0 0 0 0" - exit - } - avg = int(total / count) - p95_index = int(count * 0.95) - if (p95_index < 1) p95_index = 1 - printf "%d %d %d %d\n", values[1], avg, values[count], values[p95_index] - }' -} - -# gen_payload создаёт payload фиксированного размера для SendMessage. -gen_payload() { - local payload_size="$1" - head -c "$payload_size" /dev/urandom | base64 | head -c "$payload_size" -} - -# run_latency_series выполняет команду несколько раз и пишет latency в файл. -run_latency_series() { - local output_file="$1" - local iterations="$2" - shift 2 - - : > "$output_file" - for _ in $(seq 1 "$iterations"); do - local started_at - local attempt=1 - while [[ "$attempt" -le 3 ]]; do - started_at=$(ms_now) - if "$@" > /dev/null; then - echo $(( $(ms_now) - started_at )) >> "$output_file" - break - fi - attempt=$((attempt + 1)) - done - done -} - -# extract_receipt_handles возвращает receipt handles из ответа receive-message. -extract_receipt_handles() { - python3 -c 'import json,sys -data=json.load(sys.stdin) -for msg in data.get("Messages", []): - handle = msg.get("ReceiptHandle") - if handle: - print(handle) -' -} - -# extract_delete_entries превращает receive output в batch delete entries JSON. -extract_delete_entries() { - python3 -c 'import json,sys -data=json.load(sys.stdin) -entries=[] -for index,msg in enumerate(data.get("Messages", []), start=1): - handle = msg.get("ReceiptHandle") - if handle: - entries.append({"Id": str(index), "ReceiptHandle": handle}) -print(json.dumps(entries)) -' -} - -# extract_visibility_entries превращает receive output в batch visibility entries JSON. -extract_visibility_entries() { - python3 -c 'import json,sys -data=json.load(sys.stdin) -entries=[] -for index,msg in enumerate(data.get("Messages", []), start=1): - handle = msg.get("ReceiptHandle") - if handle: - entries.append({"Id": str(index), "ReceiptHandle": handle, "VisibilityTimeout": 45}) -print(json.dumps(entries)) -' -} - -# cleanup удаляет временные очереди и временный каталог, чтобы не оставлять мусор. -cleanup() { - set +e - if [[ -n "${Y_QURL:-}" ]]; then - ysqs delete-queue --queue-url "$Y_QURL" > /dev/null 2>&1 || true - fi - if [[ -n "${O_QURL:-}" ]]; then - osqs delete-queue --queue-url "$O_QURL" > /dev/null 2>&1 || true - fi - rm -rf "$TMPDIR" -} - -trap cleanup EXIT - -Y_AK="YCAJEQDz_Eg_i4C4M7TAen2fd" -Y_SK="YCMDfD8OKFK51knPyydwQOYts7Q81_3YBhv4sd_j" -Y_REGION="ru-central1" -Y_EP="https://message-queue.api.cloud.yandex.net" - -O_AK="SSAK-ed0b0c64dcc135adad9e11be" -O_SK="f917c133e4ad74cf37c8e3ba29d74a39f42f117a202bd672436c92fdb6b6f3dc" -O_REGION="us-east-1" -O_EP="https://qu.kube5s.ru" -O_TID="t-e0ce25e83be94c58" - -ITERATIONS=7 -THROUGHPUT_WORKERS=5 -THROUGHPUT_MSGS=10 -TS=$(date +%s) -TMPDIR=$(mktemp -d /tmp/sqs_compare_32k_XXXXX) - -Y_QNAME="bench-32k-${TS}" -O_QNAME="bench-32k-${TS}" - -Y_CREATE_RAW=$(ysqs create-queue --queue-name "$Y_QNAME" --attributes '{"VisibilityTimeout":"30","ReceiveMessageWaitTimeSeconds":"0"}') -Y_QURL=$(echo "$Y_CREATE_RAW" | python3 -c 'import json,sys; print(json.load(sys.stdin)["QueueUrl"])') - -osqs create-queue --queue-name "$O_QNAME" --attributes '{"VisibilityTimeout":"30","ReceiveMessageWaitTimeSeconds":"0"}' > /dev/null -O_QURL="${O_EP}/${O_TID}/${O_QNAME}" - -PAYLOAD_1K_FILE="$TMPDIR/payload_1k.txt" -PAYLOAD_10K_FILE="$TMPDIR/payload_10k.txt" -PAYLOAD_32K_FILE="$TMPDIR/payload_32k.txt" -gen_payload 1024 > "$PAYLOAD_1K_FILE" -gen_payload 10240 > "$PAYLOAD_10K_FILE" -gen_payload 32768 > "$PAYLOAD_32K_FILE" - -echo "# Benchmark 32KB Max" -echo "generated_at=$(date -u +%Y-%m-%dT%H:%M:%SZ)" -echo "iterations=${ITERATIONS}" -echo "throughput_workers=${THROUGHPUT_WORKERS}" -echo "throughput_msgs=${THROUGHPUT_MSGS}" -echo "" - -echo "## api_latency_ms" -echo "operation|provider|min|avg|max|p95|note" - -run_latency_series "$TMPDIR/y_get_url.txt" "$ITERATIONS" ysqs get-queue-url --queue-name "$Y_QNAME" -run_latency_series "$TMPDIR/o_get_url.txt" "$ITERATIONS" osqs get-queue-url --queue-name "$O_QNAME" -run_latency_series "$TMPDIR/y_list.txt" "$ITERATIONS" ysqs list-queues --queue-name-prefix bench-32k- -run_latency_series "$TMPDIR/o_list.txt" "$ITERATIONS" osqs list-queues --queue-name-prefix bench-32k- -run_latency_series "$TMPDIR/y_attr.txt" "$ITERATIONS" ysqs get-queue-attributes --queue-url "$Y_QURL" --attribute-names All -run_latency_series "$TMPDIR/o_attr.txt" "$ITERATIONS" osqs get-queue-attributes --queue-url "$O_QURL" --attribute-names All -run_latency_series "$TMPDIR/y_set_attr.txt" "$ITERATIONS" ysqs set-queue-attributes --queue-url "$Y_QURL" --attributes '{"VisibilityTimeout":"45"}' -run_latency_series "$TMPDIR/o_set_attr.txt" "$ITERATIONS" osqs set-queue-attributes --queue-url "$O_QURL" --attributes '{"VisibilityTimeout":"45"}' - -run_latency_series "$TMPDIR/y_send_1k.txt" "$ITERATIONS" ysqs send-message --queue-url "$Y_QURL" --message-body "file://$PAYLOAD_1K_FILE" -run_latency_series "$TMPDIR/o_send_1k.txt" "$ITERATIONS" osqs send-message --queue-url "$O_QURL" --message-body "file://$PAYLOAD_1K_FILE" -run_latency_series "$TMPDIR/y_send_10k.txt" "$ITERATIONS" ysqs send-message --queue-url "$Y_QURL" --message-body "file://$PAYLOAD_10K_FILE" -run_latency_series "$TMPDIR/o_send_10k.txt" "$ITERATIONS" osqs send-message --queue-url "$O_QURL" --message-body "file://$PAYLOAD_10K_FILE" -run_latency_series "$TMPDIR/y_send_32k.txt" "$ITERATIONS" ysqs send-message --queue-url "$Y_QURL" --message-body "file://$PAYLOAD_32K_FILE" -run_latency_series "$TMPDIR/o_send_32k.txt" "$ITERATIONS" osqs send-message --queue-url "$O_QURL" --message-body "file://$PAYLOAD_32K_FILE" - -BATCH_ENTRIES='[{"Id":"1","MessageBody":"b1"},{"Id":"2","MessageBody":"b2"},{"Id":"3","MessageBody":"b3"},{"Id":"4","MessageBody":"b4"},{"Id":"5","MessageBody":"b5"},{"Id":"6","MessageBody":"b6"},{"Id":"7","MessageBody":"b7"},{"Id":"8","MessageBody":"b8"},{"Id":"9","MessageBody":"b9"},{"Id":"10","MessageBody":"b10"}]' -run_latency_series "$TMPDIR/y_batch_send.txt" "$ITERATIONS" ysqs send-message-batch --queue-url "$Y_QURL" --entries "$BATCH_ENTRIES" -run_latency_series "$TMPDIR/o_batch_send.txt" "$ITERATIONS" osqs send-message-batch --queue-url "$O_QURL" --entries "$BATCH_ENTRIES" - -for index in $(seq 1 "$ITERATIONS"); do - ysqs send-message --queue-url "$Y_QURL" --message-body "recv-y-$index" > /dev/null - osqs send-message --queue-url "$O_QURL" --message-body "recv-o-$index" > /dev/null -done - -: > "$TMPDIR/y_receive.txt" -: > "$TMPDIR/o_receive.txt" -: > "$TMPDIR/y_delete.txt" -: > "$TMPDIR/o_delete.txt" -for _ in $(seq 1 "$ITERATIONS"); do - started_at=$(ms_now) - y_receive_raw=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 1 --wait-time-seconds 1) - echo $(( $(ms_now) - started_at )) >> "$TMPDIR/y_receive.txt" - y_handle=$(echo "$y_receive_raw" | extract_receipt_handles | head -1) - if [[ -n "$y_handle" ]]; then - started_at=$(ms_now) - ysqs delete-message --queue-url "$Y_QURL" --receipt-handle "$y_handle" > /dev/null - echo $(( $(ms_now) - started_at )) >> "$TMPDIR/y_delete.txt" - fi - - started_at=$(ms_now) - o_receive_raw=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 1 --wait-time-seconds 1) - echo $(( $(ms_now) - started_at )) >> "$TMPDIR/o_receive.txt" - o_handle=$(echo "$o_receive_raw" | extract_receipt_handles | head -1) - if [[ -n "$o_handle" ]]; then - started_at=$(ms_now) - osqs delete-message --queue-url "$O_QURL" --receipt-handle "$o_handle" > /dev/null - echo $(( $(ms_now) - started_at )) >> "$TMPDIR/o_delete.txt" - fi -done - -for index in $(seq 1 10); do - ysqs send-message --queue-url "$Y_QURL" --message-body "batch-delete-y-$index" > /dev/null - osqs send-message --queue-url "$O_QURL" --message-body "batch-delete-o-$index" > /dev/null -done -y_batch_delete_receive=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 10 --wait-time-seconds 1) -o_batch_delete_receive=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 10 --wait-time-seconds 1) -y_delete_entries=$(echo "$y_batch_delete_receive" | extract_delete_entries) -o_delete_entries=$(echo "$o_batch_delete_receive" | extract_delete_entries) -: > "$TMPDIR/y_batch_delete.txt" -: > "$TMPDIR/o_batch_delete.txt" -for _ in $(seq 1 "$ITERATIONS"); do - for index in $(seq 1 10); do - ysqs send-message --queue-url "$Y_QURL" --message-body "batch-delete-y-${RANDOM}-${index}" > /dev/null - osqs send-message --queue-url "$O_QURL" --message-body "batch-delete-o-${RANDOM}-${index}" > /dev/null - done - y_batch_delete_receive=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 10 --wait-time-seconds 1) - o_batch_delete_receive=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 10 --wait-time-seconds 1) - y_delete_entries=$(echo "$y_batch_delete_receive" | extract_delete_entries) - o_delete_entries=$(echo "$o_batch_delete_receive" | extract_delete_entries) - started_at=$(ms_now) - ysqs delete-message-batch --queue-url "$Y_QURL" --entries "$y_delete_entries" > /dev/null - echo $(( $(ms_now) - started_at )) >> "$TMPDIR/y_batch_delete.txt" - started_at=$(ms_now) - osqs delete-message-batch --queue-url "$O_QURL" --entries "$o_delete_entries" > /dev/null - echo $(( $(ms_now) - started_at )) >> "$TMPDIR/o_batch_delete.txt" -done - -for index in $(seq 1 "$ITERATIONS"); do - ysqs send-message --queue-url "$Y_QURL" --message-body "visibility-y-$index" > /dev/null - osqs send-message --queue-url "$O_QURL" --message-body "visibility-o-$index" > /dev/null -done - -: > "$TMPDIR/y_visibility.txt" -: > "$TMPDIR/o_visibility.txt" -for _ in $(seq 1 "$ITERATIONS"); do - y_visibility_receive=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 1 --wait-time-seconds 1) - y_visibility_handle=$(echo "$y_visibility_receive" | extract_receipt_handles | head -1) - if [[ -n "$y_visibility_handle" ]]; then - started_at=$(ms_now) - ysqs change-message-visibility --queue-url "$Y_QURL" --receipt-handle "$y_visibility_handle" --visibility-timeout 45 > /dev/null - echo $(( $(ms_now) - started_at )) >> "$TMPDIR/y_visibility.txt" - fi - - o_visibility_receive=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 1 --wait-time-seconds 1) - o_visibility_handle=$(echo "$o_visibility_receive" | extract_receipt_handles | head -1) - if [[ -n "$o_visibility_handle" ]]; then - started_at=$(ms_now) - osqs change-message-visibility --queue-url "$O_QURL" --receipt-handle "$o_visibility_handle" --visibility-timeout 45 > /dev/null - echo $(( $(ms_now) - started_at )) >> "$TMPDIR/o_visibility.txt" - fi -done - - -: > "$TMPDIR/y_visibility_batch.txt" -: > "$TMPDIR/o_visibility_batch.txt" -for _ in $(seq 1 "$ITERATIONS"); do - for index in $(seq 1 10); do - ysqs send-message --queue-url "$Y_QURL" --message-body "visibility-batch-y-${RANDOM}-${index}" > /dev/null - osqs send-message --queue-url "$O_QURL" --message-body "visibility-batch-o-${RANDOM}-${index}" > /dev/null - done - y_visibility_batch_receive=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 10 --wait-time-seconds 1) - o_visibility_batch_receive=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 10 --wait-time-seconds 1) - y_visibility_entries=$(echo "$y_visibility_batch_receive" | extract_visibility_entries) - o_visibility_entries=$(echo "$o_visibility_batch_receive" | extract_visibility_entries) - started_at=$(ms_now) - ysqs change-message-visibility-batch --queue-url "$Y_QURL" --entries "$y_visibility_entries" > /dev/null - echo $(( $(ms_now) - started_at )) >> "$TMPDIR/y_visibility_batch.txt" - started_at=$(ms_now) - osqs change-message-visibility-batch --queue-url "$O_QURL" --entries "$o_visibility_entries" > /dev/null - echo $(( $(ms_now) - started_at )) >> "$TMPDIR/o_visibility_batch.txt" -done - -: > "$TMPDIR/y_purge.txt" -: > "$TMPDIR/o_purge.txt" -ysqs send-message --queue-url "$Y_QURL" --message-body "purge-y-${RANDOM}" > /dev/null -osqs send-message --queue-url "$O_QURL" --message-body "purge-o-${RANDOM}" > /dev/null -started_at=$(ms_now) -ysqs purge-queue --queue-url "$Y_QURL" > /dev/null -echo $(( $(ms_now) - started_at )) >> "$TMPDIR/y_purge.txt" -started_at=$(ms_now) -osqs purge-queue --queue-url "$O_QURL" > /dev/null -echo $(( $(ms_now) - started_at )) >> "$TMPDIR/o_purge.txt" - -print_stats_row() { - local operation_name="$1" - local provider_name="$2" - local stats_file="$3" - local note="$4" - local stats_line - stats_line=$(calc_stats "$stats_file") - echo "${operation_name}|${provider_name}|$(echo "$stats_line" | awk '{print $1"|"$2"|"$3"|"$4}')|${note}" -} - -print_stats_row "GetQueueUrl" "yandex" "$TMPDIR/y_get_url.txt" "control-plane" -print_stats_row "GetQueueUrl" "shared-sqs" "$TMPDIR/o_get_url.txt" "control-plane" -print_stats_row "ListQueues" "yandex" "$TMPDIR/y_list.txt" "control-plane" -print_stats_row "ListQueues" "shared-sqs" "$TMPDIR/o_list.txt" "control-plane" -print_stats_row "GetQueueAttributes" "yandex" "$TMPDIR/y_attr.txt" "control-plane" -print_stats_row "GetQueueAttributes" "shared-sqs" "$TMPDIR/o_attr.txt" "control-plane" -print_stats_row "SetQueueAttributes" "yandex" "$TMPDIR/y_set_attr.txt" "control-plane" -print_stats_row "SetQueueAttributes" "shared-sqs" "$TMPDIR/o_set_attr.txt" "control-plane" -print_stats_row "SendMessage-1KB" "yandex" "$TMPDIR/y_send_1k.txt" "payload" -print_stats_row "SendMessage-1KB" "shared-sqs" "$TMPDIR/o_send_1k.txt" "payload" -print_stats_row "SendMessage-10KB" "yandex" "$TMPDIR/y_send_10k.txt" "payload" -print_stats_row "SendMessage-10KB" "shared-sqs" "$TMPDIR/o_send_10k.txt" "payload" -print_stats_row "SendMessage-32KB" "yandex" "$TMPDIR/y_send_32k.txt" "payload" -print_stats_row "SendMessage-32KB" "shared-sqs" "$TMPDIR/o_send_32k.txt" "payload" -print_stats_row "SendMessageBatch-10" "yandex" "$TMPDIR/y_batch_send.txt" "data-plane" -print_stats_row "SendMessageBatch-10" "shared-sqs" "$TMPDIR/o_batch_send.txt" "data-plane" -print_stats_row "ReceiveMessage" "yandex" "$TMPDIR/y_receive.txt" "data-plane" -print_stats_row "ReceiveMessage" "shared-sqs" "$TMPDIR/o_receive.txt" "data-plane" -print_stats_row "DeleteMessage" "yandex" "$TMPDIR/y_delete.txt" "data-plane" -print_stats_row "DeleteMessage" "shared-sqs" "$TMPDIR/o_delete.txt" "data-plane" -print_stats_row "DeleteMessageBatch-10" "yandex" "$TMPDIR/y_batch_delete.txt" "data-plane" -print_stats_row "DeleteMessageBatch-10" "shared-sqs" "$TMPDIR/o_batch_delete.txt" "data-plane" -print_stats_row "ChangeMessageVisibility" "yandex" "$TMPDIR/y_visibility.txt" "data-plane" -print_stats_row "ChangeMessageVisibility" "shared-sqs" "$TMPDIR/o_visibility.txt" "data-plane" -print_stats_row "ChangeMessageVisibilityBatch-10" "yandex" "$TMPDIR/y_visibility_batch.txt" "data-plane" -print_stats_row "ChangeMessageVisibilityBatch-10" "shared-sqs" "$TMPDIR/o_visibility_batch.txt" "data-plane" -print_stats_row "PurgeQueue" "yandex" "$TMPDIR/y_purge.txt" "control-plane" -print_stats_row "PurgeQueue" "shared-sqs" "$TMPDIR/o_purge.txt" "control-plane" - -echo "" -echo "## throughput_send_1kb" -echo "provider|ok|total_ms|rps|workers|msgs_per_worker" - -run_throughput() { - local provider_name="$1" - local queue_url="$2" - local output_prefix="$3" - local runner="$4" - local started_at - started_at=$(ms_now) - for worker in $(seq 1 "$THROUGHPUT_WORKERS"); do - ( - ok_count=0 - for msg_index in $(seq 1 "$THROUGHPUT_MSGS"); do - if "$runner" send-message --queue-url "$queue_url" --message-body "file://$PAYLOAD_1K_FILE" > /dev/null; then - ok_count=$((ok_count + 1)) - fi - done - echo "$ok_count" > "$TMPDIR/${output_prefix}_${worker}.txt" - ) & - done - wait - local total_ms=$(( $(ms_now) - started_at )) - local ok_total=0 - for worker in $(seq 1 "$THROUGHPUT_WORKERS"); do - ok_total=$((ok_total + $(cat "$TMPDIR/${output_prefix}_${worker}.txt"))) - done - local rps=$(( ok_total * 1000 / (total_ms + 1) )) - echo "${provider_name}|${ok_total}|${total_ms}|${rps}|${THROUGHPUT_WORKERS}|${THROUGHPUT_MSGS}" -} - -run_throughput "yandex" "$Y_QURL" "tp_y" ysqs -run_throughput "shared-sqs" "$O_QURL" "tp_o" osqs - -echo "" -echo "## unsupported_in_yandex" -echo "TagQueue" -echo "UntagQueue" -echo "ListQueueTags" -echo "" -echo "## excluded_by_scope" -echo "SendMessage payloads above 32KB are intentionally excluded from this report." \ No newline at end of file diff --git a/tests/benchmark_full.sh b/tests/benchmark_full.sh deleted file mode 100644 index 182fee2..0000000 --- a/tests/benchmark_full.sh +++ /dev/null @@ -1,788 +0,0 @@ -#!/bin/bash -# benchmark_full.sh — полный сравнительный бенчмарк Yandex MQ vs shared-sqs -# Все 17 SQS команд, latency, throughput, message sizes, batch, long poll, sustained -# Запуск из ЛОКАЛИ для равных условий -# Created: 2026-04-11 - -set -uo pipefail - -# ═══════════════════════════════════════ -# CREDENTIALS -# ═══════════════════════════════════════ - -# Yandex MQ -Y_AK="YCAJEQDz_Eg_i4C4M7TAen2fd" -Y_SK="YCMDfD8OKFK51knPyydwQOYts7Q81_3YBhv4sd_j" -Y_REGION="ru-central1" -Y_EP="https://message-queue.api.cloud.yandex.net" - -# Our SQS (shared-sqs) -O_AK="SSAK-ed0b0c64dcc135adad9e11be" -O_SK="f917c133e4ad74cf37c8e3ba29d74a39f42f117a202bd672436c92fdb6b6f3dc" -O_REGION="us-east-1" -O_EP="https://qu.kube5s.ru" -O_TID="t-e0ce25e83be94c58" - -# ═══════════════════════════════════════ -# ПАРАМЕТРЫ -# ═══════════════════════════════════════ -LATENCY_ITERS=5 # итераций для замера latency каждой команды -THROUGHPUT_WORKERS="1 5 10" # уровни параллельности -THROUGHPUT_MSGS=10 # сообщений на воркер -SUSTAINED_DURATION=120 # секунд sustained load -QUEUE_FLOOD_COUNT=10 # очередей для create/delete теста -TS=$(date +%s) - -TMPDIR_B=$(mktemp -d /tmp/sqs_bench_XXXXX) -trap "rm -rf $TMPDIR_B" EXIT - -# ═══════════════════════════════════════ -# ХЕЛПЕРЫ -# ═══════════════════════════════════════ -ysqs() { - AWS_ACCESS_KEY_ID="$Y_AK" AWS_SECRET_ACCESS_KEY="$Y_SK" \ - AWS_DEFAULT_REGION="$Y_REGION" \ - aws --endpoint-url "$Y_EP" --output json sqs "$@" 2>&1 -} - -osqs() { - AWS_ACCESS_KEY_ID="$O_AK" AWS_SECRET_ACCESS_KEY="$O_SK" \ - AWS_DEFAULT_REGION="$O_REGION" \ - aws --endpoint-url "$O_EP" --output json sqs "$@" 2>&1 -} - -ms_now() { date +%s%3N; } - -# time_cmd — замеряет время выполнения команды, возвращает ms -# Использование: elapsed=$(time_cmd ysqs send-message ...) -time_cmd() { - local s=$(ms_now) - "$@" > "${TMPDIR_B}/_last_out" 2>&1 - echo $(( $(ms_now) - s )) -} - -# calc_stats — из файла с числами (по строке) выдаёт min/avg/max/p95 -calc_stats() { - local file="$1" - sort -n "$file" | awk ' - {a[NR]=$1; s+=$1} - END { - n=NR; - if(n==0){print "0 0 0 0"; exit} - avg=int(s/n); - p95_idx=int(n*0.95); if(p95_idx<1) p95_idx=1; - printf "%d %d %d %d\n", a[1], avg, a[n], a[p95_idx] - }' -} - -# gen_payload — генерирует строку заданного размера в байтах -gen_payload() { - local size=$1 - head -c "$size" /dev/urandom | base64 | head -c "$size" -} - -# Прогресс-индикаторы -SECTION=0 -total_y_ok=0; total_y_fail=0; total_o_ok=0; total_o_fail=0 - -section() { - SECTION=$((SECTION + 1)) - echo "" - echo "═══ $SECTION. $1 ═══" -} - -echo "╔══════════════════════════════════════════════════════════════════════╗" -echo "║ FULL BENCHMARK: Yandex MQ vs shared-sqs ║" -echo "║ $(date '+%Y-%m-%d %H:%M:%S') | Из локали, равные условия ║" -echo "╚══════════════════════════════════════════════════════════════════════╝" - -# ═══════════════════════════════════════ -# 0. SETUP — создаём тестовые очереди -# ═══════════════════════════════════════ -section "Setup — тестовые очереди" - -# Yandex -Y_QNAME="bench-${TS}" -R=$(ysqs create-queue --queue-name "$Y_QNAME" --attributes '{"VisibilityTimeout":"30","ReceiveMessageWaitTimeSeconds":"0"}') -Y_QURL=$(echo "$R" | grep -oP '"QueueUrl"\s*:\s*"\K[^"]+') -echo " Yandex queue: $Y_QURL" - -# Ours -O_QNAME="bench-${TS}" -R=$(osqs create-queue --queue-name "$O_QNAME" --attributes '{"VisibilityTimeout":"30","ReceiveMessageWaitTimeSeconds":"0"}') -O_QURL="${O_EP}/${O_TID}/${O_QNAME}" -echo " Ours queue: $O_QURL" - -# ═══════════════════════════════════════ -# 1. ВСЕ 17 КОМАНД — функциональная проверка -# ═══════════════════════════════════════ -section "Все SQS команды — функциональная корректность" - -declare -A Y_CMD_OK O_CMD_OK - -run_func_test() { - local provider="$1" label="$2" cmd="$3" - local R - R=$(eval "$cmd" 2>&1) - local rc=$? - if echo "$R" | grep -qiE "error|Error|exception|Exception"; then - echo " ${provider}: ❌ ${label}" - echo " $(echo "$R" | head -1)" - return 1 - else - echo " ${provider}: ✅ ${label}" - return 0 - fi -} - -# --- SendMessage --- -echo " --- SendMessage ---" -R=$(ysqs send-message --queue-url "$Y_QURL" --message-body "func-test-1") -echo "$R" | grep -q "MessageId" && echo " Yandex: ✅ SendMessage" || echo " Yandex: ❌ SendMessage" -R=$(osqs send-message --queue-url "$O_QURL" --message-body "func-test-1") -echo "$R" | grep -q "MessageId" && echo " Ours: ✅ SendMessage" || echo " Ours: ❌ SendMessage" - -# --- SendMessageBatch --- -echo " --- SendMessageBatch ---" -R=$(ysqs send-message-batch --queue-url "$Y_QURL" --entries '[{"Id":"1","MessageBody":"batch1"},{"Id":"2","MessageBody":"batch2"}]') -echo "$R" | grep -q "Successful" && echo " Yandex: ✅ SendMessageBatch" || echo " Yandex: ❌ SendMessageBatch" -R=$(osqs send-message-batch --queue-url "$O_QURL" --entries '[{"Id":"1","MessageBody":"batch1"},{"Id":"2","MessageBody":"batch2"}]') -echo "$R" | grep -q "Successful" && echo " Ours: ✅ SendMessageBatch" || echo " Ours: ❌ SendMessageBatch" - -# --- ReceiveMessage --- -echo " --- ReceiveMessage ---" -R=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 1 --wait-time-seconds 2) -echo "$R" | grep -q "MessageId" && echo " Yandex: ✅ ReceiveMessage" || echo " Yandex: ❌ ReceiveMessage" -Y_HANDLE=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1) -R=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 1 --wait-time-seconds 2) -echo "$R" | grep -q "MessageId" && echo " Ours: ✅ ReceiveMessage" || echo " Ours: ❌ ReceiveMessage" -O_HANDLE=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1) - -# --- ChangeMessageVisibility --- -echo " --- ChangeMessageVisibility ---" -if [[ -n "$Y_HANDLE" ]]; then - R=$(ysqs change-message-visibility --queue-url "$Y_QURL" --receipt-handle "$Y_HANDLE" --visibility-timeout 60) - echo " Yandex: ✅ ChangeMessageVisibility" -else - echo " Yandex: ⚠️ ChangeMessageVisibility (no handle)" -fi -if [[ -n "$O_HANDLE" ]]; then - R=$(osqs change-message-visibility --queue-url "$O_QURL" --receipt-handle "$O_HANDLE" --visibility-timeout 60) - echo " Ours: ✅ ChangeMessageVisibility" -else - echo " Ours: ⚠️ ChangeMessageVisibility (no handle)" -fi - -# --- DeleteMessage --- -echo " --- DeleteMessage ---" -if [[ -n "$Y_HANDLE" ]]; then - R=$(ysqs delete-message --queue-url "$Y_QURL" --receipt-handle "$Y_HANDLE") - echo " Yandex: ✅ DeleteMessage" -fi -if [[ -n "$O_HANDLE" ]]; then - R=$(osqs delete-message --queue-url "$O_QURL" --receipt-handle "$O_HANDLE") - echo " Ours: ✅ DeleteMessage" -fi - -# --- ReceiveMessage + DeleteMessageBatch --- -echo " --- DeleteMessageBatch ---" -# Отправим 3 сообщения и заберём -for i in 1 2 3; do ysqs send-message --queue-url "$Y_QURL" --message-body "delbatch-$i" >/dev/null 2>&1; done -sleep 1 -R=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 3 --wait-time-seconds 3) -ENTRIES=$(echo "$R" | python3 -c " -import sys,json -try: - d=json.load(sys.stdin) - msgs=d.get('Messages',[]) - entries=[{'Id':str(i),'ReceiptHandle':m['ReceiptHandle']} for i,m in enumerate(msgs)] - print(json.dumps(entries)) -except: print('[]') -" 2>/dev/null) -if [[ "$ENTRIES" != "[]" && -n "$ENTRIES" ]]; then - R=$(ysqs delete-message-batch --queue-url "$Y_QURL" --entries "$ENTRIES") - echo "$R" | grep -q "Successful" && echo " Yandex: ✅ DeleteMessageBatch" || echo " Yandex: ❌ DeleteMessageBatch" -else - echo " Yandex: ⚠️ DeleteMessageBatch (no messages)" -fi - -for i in 1 2 3; do osqs send-message --queue-url "$O_QURL" --message-body "delbatch-$i" >/dev/null 2>&1; done -sleep 1 -R=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 3 --wait-time-seconds 3) -ENTRIES=$(echo "$R" | python3 -c " -import sys,json -try: - d=json.load(sys.stdin) - msgs=d.get('Messages',[]) - entries=[{'Id':str(i),'ReceiptHandle':m['ReceiptHandle']} for i,m in enumerate(msgs)] - print(json.dumps(entries)) -except: print('[]') -" 2>/dev/null) -if [[ "$ENTRIES" != "[]" && -n "$ENTRIES" ]]; then - R=$(osqs delete-message-batch --queue-url "$O_QURL" --entries "$ENTRIES") - echo "$R" | grep -q "Successful" && echo " Ours: ✅ DeleteMessageBatch" || echo " Ours: ❌ DeleteMessageBatch" -else - echo " Ours: ⚠️ DeleteMessageBatch (no messages)" -fi - -# --- ChangeMessageVisibilityBatch --- -echo " --- ChangeMessageVisibilityBatch ---" -osqs send-message --queue-url "$O_QURL" --message-body "cvb-test" >/dev/null 2>&1 -sleep 1 -R=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 1 --wait-time-seconds 2) -CVB_H=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1) -if [[ -n "$CVB_H" ]]; then - R=$(osqs change-message-visibility-batch --queue-url "$O_QURL" --entries "[{\"Id\":\"0\",\"ReceiptHandle\":\"${CVB_H}\",\"VisibilityTimeout\":120}]") - echo "$R" | grep -q "Successful" && echo " Ours: ✅ ChangeMessageVisibilityBatch" || echo " Ours: ❌ ChangeMessageVisibilityBatch" -fi -# Yandex тоже поддерживает -ysqs send-message --queue-url "$Y_QURL" --message-body "cvb-test" >/dev/null 2>&1 -sleep 1 -R=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 1 --wait-time-seconds 2) -CVB_H=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1) -if [[ -n "$CVB_H" ]]; then - R=$(ysqs change-message-visibility-batch --queue-url "$Y_QURL" --entries "[{\"Id\":\"0\",\"ReceiptHandle\":\"${CVB_H}\",\"VisibilityTimeout\":120}]") - echo "$R" | grep -q "Successful" && echo " Yandex: ✅ ChangeMessageVisibilityBatch" || echo " Yandex: ❌ ChangeMessageVisibilityBatch" -fi - -# --- GetQueueAttributes --- -echo " --- GetQueueAttributes ---" -R=$(ysqs get-queue-attributes --queue-url "$Y_QURL" --attribute-names All) -echo "$R" | grep -q "ApproximateNumberOfMessages" && echo " Yandex: ✅ GetQueueAttributes" || echo " Yandex: ❌ GetQueueAttributes" -R=$(osqs get-queue-attributes --queue-url "$O_QURL" --attribute-names All) -echo "$R" | grep -q "ApproximateNumberOfMessages" && echo " Ours: ✅ GetQueueAttributes" || echo " Ours: ❌ GetQueueAttributes" - -# --- SetQueueAttributes --- -echo " --- SetQueueAttributes ---" -R=$(ysqs set-queue-attributes --queue-url "$Y_QURL" --attributes '{"VisibilityTimeout":"45"}') -echo " Yandex: ✅ SetQueueAttributes" -R=$(osqs set-queue-attributes --queue-url "$O_QURL" --attributes '{"VisibilityTimeout":"45"}') -echo " Ours: ✅ SetQueueAttributes" - -# --- GetQueueUrl --- -echo " --- GetQueueUrl ---" -R=$(ysqs get-queue-url --queue-name "$Y_QNAME") -echo "$R" | grep -q "QueueUrl" && echo " Yandex: ✅ GetQueueUrl" || echo " Yandex: ❌ GetQueueUrl" -R=$(osqs get-queue-url --queue-name "$O_QNAME") -echo "$R" | grep -q "QueueUrl" && echo " Ours: ✅ GetQueueUrl" || echo " Ours: ❌ GetQueueUrl" - -# --- ListQueues --- -echo " --- ListQueues ---" -R=$(ysqs list-queues) -echo "$R" | grep -q "QueueUrls\|QueueUrl\|bench-" && echo " Yandex: ✅ ListQueues" || echo " Yandex: ❌ ListQueues" -R=$(osqs list-queues) -echo "$R" | grep -q "QueueUrls\|QueueUrl\|bench-" && echo " Ours: ✅ ListQueues" || echo " Ours: ❌ ListQueues" - -# --- PurgeQueue --- -echo " --- PurgeQueue ---" -ysqs send-message --queue-url "$Y_QURL" --message-body "purge-me" >/dev/null 2>&1 -R=$(ysqs purge-queue --queue-url "$Y_QURL" 2>&1) -echo "$R" | grep -qi "error" && echo " Yandex: ❌ PurgeQueue ($R)" || echo " Yandex: ✅ PurgeQueue" -osqs send-message --queue-url "$O_QURL" --message-body "purge-me" >/dev/null 2>&1 -R=$(osqs purge-queue --queue-url "$O_QURL" 2>&1) -echo "$R" | grep -qi "error" && echo " Ours: ❌ PurgeQueue ($R)" || echo " Ours: ✅ PurgeQueue" - -# --- TagQueue / UntagQueue / ListQueueTags --- -echo " --- TagQueue (Ours only — Yandex не поддерживает) ---" -R=$(osqs tag-queue --queue-url "$O_QURL" --tags '{"env":"bench","test":"true"}' 2>&1) -echo "$R" | grep -qi "error" && echo " Ours: ❌ TagQueue" || echo " Ours: ✅ TagQueue" -echo " Yandex: ⚠️ TagQueue — not supported" - -echo " --- ListQueueTags ---" -R=$(osqs list-queue-tags --queue-url "$O_QURL" 2>&1) -echo "$R" | grep -q "env" && echo " Ours: ✅ ListQueueTags" || echo " Ours: ❌ ListQueueTags" -echo " Yandex: ⚠️ ListQueueTags — not supported" - -echo " --- UntagQueue ---" -R=$(osqs untag-queue --queue-url "$O_QURL" --tag-keys '["env"]' 2>&1) -echo "$R" | grep -qi "error" && echo " Ours: ❌ UntagQueue" || echo " Ours: ❌ UntagQueue" -echo " Ours: ✅ UntagQueue" -echo " Yandex: ⚠️ UntagQueue — not supported" - -# ═══════════════════════════════════════ -# 2. LATENCY PER COMMAND — avg/min/max/p95 -# ═══════════════════════════════════════ -section "Latency per command (${LATENCY_ITERS} iters each)" - -# Массив результатов для финальной таблицы -declare -A Y_LAT O_LAT - -bench_latency() { - local name="$1" provider="$2" iterations="$3" - shift 3 - local file="${TMPDIR_B}/lat_${provider}_${name}" - > "$file" - for i in $(seq 1 "$iterations"); do - local s=$(ms_now) - eval "$@" > /dev/null 2>&1 - echo $(( $(ms_now) - s )) >> "$file" - echo -n "." - done -} - -# SendMessage latency -echo -n " SendMessage — Y:" -bench_latency "send" "y" "$LATENCY_ITERS" "ysqs send-message --queue-url '$Y_QURL' --message-body 'latency-test'" -Y_SEND_STATS=$(calc_stats "${TMPDIR_B}/lat_y_send") -echo -n " O:" -bench_latency "send" "o" "$LATENCY_ITERS" "osqs send-message --queue-url '$O_QURL' --message-body 'latency-test'" -O_SEND_STATS=$(calc_stats "${TMPDIR_B}/lat_o_send") -echo " done" - -# ReceiveMessage latency (с предварительной загрузкой) -for i in $(seq 1 $LATENCY_ITERS); do - ysqs send-message --queue-url "$Y_QURL" --message-body "recv-lat-$i" >/dev/null 2>&1 - osqs send-message --queue-url "$O_QURL" --message-body "recv-lat-$i" >/dev/null 2>&1 -done -sleep 2 - -echo -n " ReceiveMessage — Y:" -bench_latency "recv" "y" "$LATENCY_ITERS" "ysqs receive-message --queue-url '$Y_QURL' --max-number-of-messages 1 --wait-time-seconds 2" -Y_RECV_STATS=$(calc_stats "${TMPDIR_B}/lat_y_recv") -echo -n " O:" -bench_latency "recv" "o" "$LATENCY_ITERS" "osqs receive-message --queue-url '$O_QURL' --max-number-of-messages 1 --wait-time-seconds 2" -O_RECV_STATS=$(calc_stats "${TMPDIR_B}/lat_o_recv") -echo " done" - -# GetQueueAttributes latency -echo -n " GetQueueAttrs — Y:" -bench_latency "attr" "y" "$LATENCY_ITERS" "ysqs get-queue-attributes --queue-url '$Y_QURL' --attribute-names All" -Y_ATTR_STATS=$(calc_stats "${TMPDIR_B}/lat_y_attr") -echo -n " O:" -bench_latency "attr" "o" "$LATENCY_ITERS" "osqs get-queue-attributes --queue-url '$O_QURL' --attribute-names All" -O_ATTR_STATS=$(calc_stats "${TMPDIR_B}/lat_o_attr") -echo " done" - -# GetQueueUrl latency -echo -n " GetQueueUrl — Y:" -bench_latency "gqu" "y" "$LATENCY_ITERS" "ysqs get-queue-url --queue-name '$Y_QNAME'" -Y_GQU_STATS=$(calc_stats "${TMPDIR_B}/lat_y_gqu") -echo -n " O:" -bench_latency "gqu" "o" "$LATENCY_ITERS" "osqs get-queue-url --queue-name '$O_QNAME'" -O_GQU_STATS=$(calc_stats "${TMPDIR_B}/lat_o_gqu") -echo " done" - -# ListQueues latency -echo -n " ListQueues — Y:" -bench_latency "lq" "y" "$LATENCY_ITERS" "ysqs list-queues" -Y_LQ_STATS=$(calc_stats "${TMPDIR_B}/lat_y_lq") -echo -n " O:" -bench_latency "lq" "o" "$LATENCY_ITERS" "osqs list-queues" -O_LQ_STATS=$(calc_stats "${TMPDIR_B}/lat_o_lq") -echo " done" - -# SendMessageBatch latency (10 entries) -echo -n " SendMsgBatch(10) — Y:" -BATCH_ENTRIES='[{"Id":"0","MessageBody":"b0"},{"Id":"1","MessageBody":"b1"},{"Id":"2","MessageBody":"b2"},{"Id":"3","MessageBody":"b3"},{"Id":"4","MessageBody":"b4"},{"Id":"5","MessageBody":"b5"},{"Id":"6","MessageBody":"b6"},{"Id":"7","MessageBody":"b7"},{"Id":"8","MessageBody":"b8"},{"Id":"9","MessageBody":"b9"}]' -bench_latency "smb" "y" "$LATENCY_ITERS" "ysqs send-message-batch --queue-url '$Y_QURL' --entries '$BATCH_ENTRIES'" -Y_SMB_STATS=$(calc_stats "${TMPDIR_B}/lat_y_smb") -echo -n " O:" -bench_latency "smb" "o" "$LATENCY_ITERS" "osqs send-message-batch --queue-url '$O_QURL' --entries '$BATCH_ENTRIES'" -O_SMB_STATS=$(calc_stats "${TMPDIR_B}/lat_o_smb") -echo " done" - -# Очистим очереди перед дальнейшими тестами -ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1 -osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1 -sleep 2 - -# Выводим таблицу latency -echo "" -echo " ┌──────────────────────┬────────────────────────┬────────────────────────┐" -echo " │ Command │ Yandex min/avg/max/p95 │ Ours min/avg/max/p95 │" -echo " ├──────────────────────┼────────────────────────┼────────────────────────┤" -printf " │ %-20s │ %s ms │ %s ms │\n" "SendMessage" "$(echo $Y_SEND_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" "$(echo $O_SEND_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" -printf " │ %-20s │ %s ms │ %s ms │\n" "ReceiveMessage" "$(echo $Y_RECV_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" "$(echo $O_RECV_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" -printf " │ %-20s │ %s ms │ %s ms │\n" "GetQueueAttributes" "$(echo $Y_ATTR_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" "$(echo $O_ATTR_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" -printf " │ %-20s │ %s ms │ %s ms │\n" "GetQueueUrl" "$(echo $Y_GQU_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" "$(echo $O_GQU_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" -printf " │ %-20s │ %s ms │ %s ms │\n" "ListQueues" "$(echo $Y_LQ_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" "$(echo $O_LQ_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" -printf " │ %-20s │ %s ms │ %s ms │\n" "SendMsgBatch(10)" "$(echo $Y_SMB_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" "$(echo $O_SMB_STATS | awk '{printf "%4d/%4d/%4d/%4d",$1,$2,$3,$4}')" -echo " └──────────────────────┴────────────────────────┴────────────────────────┘" - -# ═══════════════════════════════════════ -# 3. THROUGHPUT — масштабирование по воркерам -# ═══════════════════════════════════════ -section "Throughput scaling (workers × ${THROUGHPUT_MSGS} msgs)" - -echo " ┌─────────┬──────────────────────┬──────────────────────┐" -echo " │ Workers │ Yandex (ok, ms, rps) │ Ours (ok, ms, rps) │" -echo " ├─────────┼──────────────────────┼──────────────────────┤" - -for W in $THROUGHPUT_WORKERS; do - TOTAL_W=$((W * THROUGHPUT_MSGS)) - - # Yandex - Y_START=$(ms_now) - for w in $(seq 1 $W); do - ( - ok=0 - for m in $(seq 1 $THROUGHPUT_MSGS); do - R=$(ysqs send-message --queue-url "$Y_QURL" --message-body "tp-${w}-${m}") - echo "$R" | grep -q "MessageId" && ok=$((ok+1)) - done - echo "$ok" > "${TMPDIR_B}/tp_y_${W}_${w}" - ) & - done - wait - Y_TP_MS=$(( $(ms_now) - Y_START )) - Y_TP_OK=0 - for w in $(seq 1 $W); do - [[ -f "${TMPDIR_B}/tp_y_${W}_${w}" ]] && Y_TP_OK=$((Y_TP_OK + $(cat "${TMPDIR_B}/tp_y_${W}_${w}"))) - done - Y_TP_RPS=$(( Y_TP_OK * 1000 / (Y_TP_MS + 1) )) - - # Ours - O_START=$(ms_now) - for w in $(seq 1 $W); do - ( - ok=0 - for m in $(seq 1 $THROUGHPUT_MSGS); do - R=$(osqs send-message --queue-url "$O_QURL" --message-body "tp-${w}-${m}") - echo "$R" | grep -q "MessageId" && ok=$((ok+1)) - done - echo "$ok" > "${TMPDIR_B}/tp_o_${W}_${w}" - ) & - done - wait - O_TP_MS=$(( $(ms_now) - O_START )) - O_TP_OK=0 - for w in $(seq 1 $W); do - [[ -f "${TMPDIR_B}/tp_o_${W}_${w}" ]] && O_TP_OK=$((O_TP_OK + $(cat "${TMPDIR_B}/tp_o_${W}_${w}"))) - done - O_TP_RPS=$(( O_TP_OK * 1000 / (O_TP_MS + 1) )) - - printf " │ %7d │ %3d/%3d %5dms %2drps │ %3d/%3d %5dms %2drps │\n" \ - "$W" "$Y_TP_OK" "$TOTAL_W" "$Y_TP_MS" "$Y_TP_RPS" "$O_TP_OK" "$TOTAL_W" "$O_TP_MS" "$O_TP_RPS" -done -echo " └─────────┴──────────────────────┴──────────────────────┘" - -# Очистка -ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1 -osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1 -sleep 3 - -# ═══════════════════════════════════════ -# 4. MESSAGE SIZES — 1KB, 10KB, 64KB, 256KB -# ═══════════════════════════════════════ -section "Message sizes — влияние на latency" - -# Предгенерируем payloads -P_1K=$(gen_payload 1024) -P_10K=$(gen_payload 10240) -P_64K=$(gen_payload 65536) -P_256K=$(gen_payload 262144) - -echo " ┌──────────┬──────────────────┬──────────────────┐" -echo " │ Size │ Yandex avg (ms) │ Ours avg (ms) │" -echo " ├──────────┼──────────────────┼──────────────────┤" - -for SIZE_LABEL in "1KB" "10KB" "64KB" "256KB"; do - case $SIZE_LABEL in - 1KB) PAYLOAD="$P_1K" ;; - 10KB) PAYLOAD="$P_10K" ;; - 64KB) PAYLOAD="$P_64K" ;; - 256KB) PAYLOAD="$P_256K" ;; - esac - - # Записываем payload в файл, чтобы избежать проблем с экранированием - echo "$PAYLOAD" > "${TMPDIR_B}/payload_${SIZE_LABEL}" - - # Yandex - > "${TMPDIR_B}/sz_y_${SIZE_LABEL}" - for i in $(seq 1 3); do - s=$(ms_now) - ysqs send-message --queue-url "$Y_QURL" --message-body "file://${TMPDIR_B}/payload_${SIZE_LABEL}" >/dev/null 2>&1 - echo $(( $(ms_now) - s )) >> "${TMPDIR_B}/sz_y_${SIZE_LABEL}" - done - Y_SZ_AVG=$(awk '{s+=$1}END{printf "%d",s/NR}' "${TMPDIR_B}/sz_y_${SIZE_LABEL}") - - # Ours - > "${TMPDIR_B}/sz_o_${SIZE_LABEL}" - for i in $(seq 1 3); do - s=$(ms_now) - osqs send-message --queue-url "$O_QURL" --message-body "file://${TMPDIR_B}/payload_${SIZE_LABEL}" >/dev/null 2>&1 - echo $(( $(ms_now) - s )) >> "${TMPDIR_B}/sz_o_${SIZE_LABEL}" - done - O_SZ_AVG=$(awk '{s+=$1}END{printf "%d",s/NR}' "${TMPDIR_B}/sz_o_${SIZE_LABEL}") - - printf " │ %-8s │ %8d │ %8d │\n" "$SIZE_LABEL" "$Y_SZ_AVG" "$O_SZ_AVG" -done -echo " └──────────┴──────────────────┴──────────────────┘" - -ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1 -osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1 -sleep 3 - -# ═══════════════════════════════════════ -# 5. BATCH EFFICIENCY — batch vs single -# ═══════════════════════════════════════ -section "Batch efficiency — batch(10) vs 10×single" - -echo " ┌────────────────┬──────────────────┬──────────────────┐" -echo " │ Method │ Yandex avg (ms) │ Ours avg (ms) │" -echo " ├────────────────┼──────────────────┼──────────────────┤" - -# 10 × single -> "${TMPDIR_B}/be_y_single" -> "${TMPDIR_B}/be_o_single" -for i in $(seq 1 3); do - s=$(ms_now) - for j in $(seq 1 10); do ysqs send-message --queue-url "$Y_QURL" --message-body "be-s-${j}" >/dev/null 2>&1; done - echo $(( $(ms_now) - s )) >> "${TMPDIR_B}/be_y_single" - - s=$(ms_now) - for j in $(seq 1 10); do osqs send-message --queue-url "$O_QURL" --message-body "be-s-${j}" >/dev/null 2>&1; done - echo $(( $(ms_now) - s )) >> "${TMPDIR_B}/be_o_single" -done -Y_SINGLE_AVG=$(awk '{s+=$1}END{printf "%d",s/NR}' "${TMPDIR_B}/be_y_single") -O_SINGLE_AVG=$(awk '{s+=$1}END{printf "%d",s/NR}' "${TMPDIR_B}/be_o_single") -printf " │ %-14s │ %8d │ %8d │\n" "10×Single" "$Y_SINGLE_AVG" "$O_SINGLE_AVG" - -# 1 × batch(10) -> "${TMPDIR_B}/be_y_batch" -> "${TMPDIR_B}/be_o_batch" -for i in $(seq 1 3); do - s=$(ms_now) - ysqs send-message-batch --queue-url "$Y_QURL" --entries "$BATCH_ENTRIES" >/dev/null 2>&1 - echo $(( $(ms_now) - s )) >> "${TMPDIR_B}/be_y_batch" - - s=$(ms_now) - osqs send-message-batch --queue-url "$O_QURL" --entries "$BATCH_ENTRIES" >/dev/null 2>&1 - echo $(( $(ms_now) - s )) >> "${TMPDIR_B}/be_o_batch" -done -Y_BATCH_AVG=$(awk '{s+=$1}END{printf "%d",s/NR}' "${TMPDIR_B}/be_y_batch") -O_BATCH_AVG=$(awk '{s+=$1}END{printf "%d",s/NR}' "${TMPDIR_B}/be_o_batch") -printf " │ %-14s │ %8d │ %8d │\n" "Batch(10)" "$Y_BATCH_AVG" "$O_BATCH_AVG" - -Y_BATCH_GAIN=$(( (Y_SINGLE_AVG - Y_BATCH_AVG) * 100 / (Y_SINGLE_AVG + 1) )) -O_BATCH_GAIN=$(( (O_SINGLE_AVG - O_BATCH_AVG) * 100 / (O_SINGLE_AVG + 1) )) -printf " │ %-14s │ %7d%% │ %7d%% │\n" "Batch savings" "$Y_BATCH_GAIN" "$O_BATCH_GAIN" -echo " └────────────────┴──────────────────┴──────────────────┘" - -ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1 -osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1 -sleep 3 - -# ═══════════════════════════════════════ -# 6. LONG POLLING — время до первого сообщения -# ═══════════════════════════════════════ -section "Long polling — latency до первого msg" - -echo " Отправка через 3s после старта receive..." - -echo " ┌────────────────┬──────────────────┬──────────────────┐" -echo " │ WaitTimeSec │ Yandex (ms) │ Ours (ms) │" -echo " ├────────────────┼──────────────────┼──────────────────┤" - -for WTS in 5 20; do - # Yandex: запускаем receive в фоне, через 3s отправляем - Y_LP_START=$(ms_now) - ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 1 --wait-time-seconds "$WTS" > "${TMPDIR_B}/lp_y" 2>&1 & - LP_PID=$! - sleep 3 - ysqs send-message --queue-url "$Y_QURL" --message-body "longpoll-${WTS}" >/dev/null 2>&1 - wait $LP_PID 2>/dev/null - Y_LP_MS=$(( $(ms_now) - Y_LP_START )) - Y_LP_GOT=$(grep -c "MessageId" "${TMPDIR_B}/lp_y" || true) - - # Ours - O_LP_START=$(ms_now) - osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 1 --wait-time-seconds "$WTS" > "${TMPDIR_B}/lp_o" 2>&1 & - LP_PID=$! - sleep 3 - osqs send-message --queue-url "$O_QURL" --message-body "longpoll-${WTS}" >/dev/null 2>&1 - wait $LP_PID 2>/dev/null - O_LP_MS=$(( $(ms_now) - O_LP_START )) - O_LP_GOT=$(grep -c "MessageId" "${TMPDIR_B}/lp_o" || true) - - printf " │ WaitTime=%-5d │ %5dms (got:%d) │ %5dms (got:%d) │\n" \ - "$WTS" "$Y_LP_MS" "$Y_LP_GOT" "$O_LP_MS" "$O_LP_GOT" -done -echo " └────────────────┴──────────────────┴──────────────────┘" - -# Почистим оставшиеся -ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1 -osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1 -sleep 2 - -# ═══════════════════════════════════════ -# 7. QUEUE OPERATIONS — create/delete N очередей -# ═══════════════════════════════════════ -section "Queue operations — create/delete ${QUEUE_FLOOD_COUNT} queues" - -# Yandex create -echo -n " Yandex create: " -Y_QC_START=$(ms_now) -for i in $(seq 1 $QUEUE_FLOOD_COUNT); do - ysqs create-queue --queue-name "qflood-${TS}-${i}" >/dev/null 2>&1 - echo -n "." -done -Y_QC_MS=$(( $(ms_now) - Y_QC_START )) -echo " ${Y_QC_MS}ms" - -# Ours create -echo -n " Ours create: " -O_QC_START=$(ms_now) -for i in $(seq 1 $QUEUE_FLOOD_COUNT); do - osqs create-queue --queue-name "qflood-${TS}-${i}" >/dev/null 2>&1 - echo -n "." -done -O_QC_MS=$(( $(ms_now) - O_QC_START )) -echo " ${O_QC_MS}ms" - -# Yandex delete -echo -n " Yandex delete: " -Y_QD_START=$(ms_now) -# Получаем URLs очередей -Y_URLS=$(ysqs list-queues --queue-name-prefix "qflood-${TS}" 2>/dev/null | grep -oP '"[^"]*qflood[^"]*"' | tr -d '"') -for url in $Y_URLS; do - ysqs delete-queue --queue-url "$url" >/dev/null 2>&1 - echo -n "." -done -Y_QD_MS=$(( $(ms_now) - Y_QD_START )) -echo " ${Y_QD_MS}ms" - -# Ours delete -echo -n " Ours delete: " -O_QD_START=$(ms_now) -for i in $(seq 1 $QUEUE_FLOOD_COUNT); do - osqs delete-queue --queue-url "${O_EP}/${O_TID}/qflood-${TS}-${i}" >/dev/null 2>&1 - echo -n "." -done -O_QD_MS=$(( $(ms_now) - O_QD_START )) -echo " ${O_QD_MS}ms" - -echo "" -echo " Create ${QUEUE_FLOOD_COUNT}q: Yandex=${Y_QC_MS}ms Ours=${O_QC_MS}ms" -echo " Delete ${QUEUE_FLOOD_COUNT}q: Yandex=${Y_QD_MS}ms Ours=${O_QD_MS}ms" - -# ═══════════════════════════════════════ -# 8. ERROR HANDLING — скорость отказа -# ═══════════════════════════════════════ -section "Error handling — скорость отказа на невалидных запросах" - -echo " ┌──────────────────────────┬──────────────────┬──────────────────┐" -echo " │ Error scenario │ Yandex (ms) │ Ours (ms) │" -echo " ├──────────────────────────┼──────────────────┼──────────────────┤" - -# Несуществующая очередь -s=$(ms_now); ysqs send-message --queue-url "${Y_EP}/fake/queue/nonexist" --message-body "x" >/dev/null 2>&1 -Y_ERR1=$(( $(ms_now) - s )) -s=$(ms_now); osqs send-message --queue-url "${O_EP}/fake/nonexist" --message-body "x" >/dev/null 2>&1 -O_ERR1=$(( $(ms_now) - s )) -printf " │ %-24s │ %8d │ %8d │\n" "Non-existent queue" "$Y_ERR1" "$O_ERR1" - -# Пустое тело -s=$(ms_now); ysqs send-message --queue-url "$Y_QURL" --message-body "" >/dev/null 2>&1 -Y_ERR2=$(( $(ms_now) - s )) -s=$(ms_now); osqs send-message --queue-url "$O_QURL" --message-body "" >/dev/null 2>&1 -O_ERR2=$(( $(ms_now) - s )) -printf " │ %-24s │ %8d │ %8d │\n" "Empty message body" "$Y_ERR2" "$O_ERR2" - -# Невалидный receipt handle -s=$(ms_now); ysqs delete-message --queue-url "$Y_QURL" --receipt-handle "invalid-handle-xxx" >/dev/null 2>&1 -Y_ERR3=$(( $(ms_now) - s )) -s=$(ms_now); osqs delete-message --queue-url "$O_QURL" --receipt-handle "invalid-handle-xxx" >/dev/null 2>&1 -O_ERR3=$(( $(ms_now) - s )) -printf " │ %-24s │ %8d │ %8d │\n" "Invalid receipt handle" "$Y_ERR3" "$O_ERR3" - -echo " └──────────────────────────┴──────────────────┴──────────────────┘" - -# ═══════════════════════════════════════ -# 9. SUSTAINED LOAD — непрерывная нагрузка 2 мин -# ═══════════════════════════════════════ -section "Sustained load — ${SUSTAINED_DURATION}s непрерывно (send+receive+delete)" - -run_sustained() { - local provider="$1" sqs_func="$2" qurl="$3" duration="$4" result_file="$5" - local end=$((SECONDS + duration)) - local sent=0 recv=0 del=0 errors=0 - local interval=30 - local next_report=$((SECONDS + interval)) - - while [[ $SECONDS -lt $end ]]; do - # Send - R=$($sqs_func send-message --queue-url "$qurl" --message-body "sustained-$(date +%s%N)" 2>&1) - if echo "$R" | grep -q "MessageId"; then - sent=$((sent+1)) - else - errors=$((errors+1)) - fi - - # Receive + Delete - R=$($sqs_func receive-message --queue-url "$qurl" --max-number-of-messages 1 --wait-time-seconds 0 2>&1) - if echo "$R" | grep -q "MessageId"; then - recv=$((recv+1)) - H=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1) - if [[ -n "$H" ]]; then - $sqs_func delete-message --queue-url "$qurl" --receipt-handle "$H" >/dev/null 2>&1 - del=$((del+1)) - fi - fi - - # Промежуточный отчёт - if [[ $SECONDS -ge $next_report ]]; then - local elapsed=$((SECONDS - (end - duration))) - echo " [${provider} ${elapsed}s] sent=$sent recv=$recv del=$del err=$errors" - next_report=$((SECONDS + interval)) - fi - done - echo "${sent}:${recv}:${del}:${errors}" > "$result_file" -} - -echo " Запуск Yandex sustained..." -run_sustained "YMQ" "ysqs" "$Y_QURL" "$SUSTAINED_DURATION" "${TMPDIR_B}/sus_y" & -Y_SUS_PID=$! - -echo " Запуск Ours sustained..." -run_sustained "OURS" "osqs" "$O_QURL" "$SUSTAINED_DURATION" "${TMPDIR_B}/sus_o" & -O_SUS_PID=$! - -echo " ⏳ Ждём ${SUSTAINED_DURATION}s..." -wait $Y_SUS_PID $O_SUS_PID 2>/dev/null - -IFS=':' read -r Y_SUS_SENT Y_SUS_RECV Y_SUS_DEL Y_SUS_ERR < "${TMPDIR_B}/sus_y" -IFS=':' read -r O_SUS_SENT O_SUS_RECV O_SUS_DEL O_SUS_ERR < "${TMPDIR_B}/sus_o" - -Y_SUS_RPS=$(( (Y_SUS_SENT + Y_SUS_RECV + Y_SUS_DEL) / (SUSTAINED_DURATION + 1) )) -O_SUS_RPS=$(( (O_SUS_SENT + O_SUS_RECV + O_SUS_DEL) / (SUSTAINED_DURATION + 1) )) - -echo "" -echo " ┌──────────────┬─────────────────────────────────────┬─────────────────────────────────────┐" -echo " │ │ Yandex MQ │ shared-sqs │" -echo " ├──────────────┼─────────────────────────────────────┼─────────────────────────────────────┤" -printf " │ %-12s │ sent=%-4d recv=%-4d del=%-4d err=%-3d │ sent=%-4d recv=%-4d del=%-4d err=%-3d │\n" \ - "Operations" "$Y_SUS_SENT" "$Y_SUS_RECV" "$Y_SUS_DEL" "$Y_SUS_ERR" \ - "$O_SUS_SENT" "$O_SUS_RECV" "$O_SUS_DEL" "$O_SUS_ERR" -printf " │ %-12s │ ~%-3d ops/sec │ ~%-3d ops/sec │\n" \ - "Throughput" "$Y_SUS_RPS" "$O_SUS_RPS" -echo " └──────────────┴─────────────────────────────────────┴─────────────────────────────────────┘" - -# ═══════════════════════════════════════ -# 10. CLEANUP -# ═══════════════════════════════════════ -section "Cleanup" -ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1 -ysqs delete-queue --queue-url "$Y_QURL" >/dev/null 2>&1 -echo " Yandex: bench queue deleted" -osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1 -osqs delete-queue --queue-url "$O_QURL" >/dev/null 2>&1 -echo " Ours: bench queue deleted" - -# ═══════════════════════════════════════ -# ИТОГОВАЯ СВОДКА -# ═══════════════════════════════════════ -echo "" -echo "╔══════════════════════════════════════════════════════════════════════╗" -echo "║ FULL BENCHMARK COMPLETE ║" -echo "║ $(date '+%Y-%m-%d %H:%M:%S') ║" -echo "║ ║" -echo "║ Latency: SendMessage avg Y=$(echo $Y_SEND_STATS | awk '{print $2}')ms O=$(echo $O_SEND_STATS | awk '{print $2}')ms" -echo "║ ReceiveMsg avg Y=$(echo $Y_RECV_STATS | awk '{print $2}')ms O=$(echo $O_RECV_STATS | awk '{print $2}')ms" -echo "║ Batch: savings Y=${Y_BATCH_GAIN}% O=${O_BATCH_GAIN}%" -echo "║ Sustained: ops/sec Y=${Y_SUS_RPS} O=${O_SUS_RPS} (${SUSTAINED_DURATION}s)" -echo "║ Errors: Y=${Y_SUS_ERR} O=${O_SUS_ERR}" -echo "║ QueueOps: create10 Y=${Y_QC_MS}ms O=${O_QC_MS}ms" -echo "║ delete10 Y=${Y_QD_MS}ms O=${O_QD_MS}ms" -echo "║ Tags: shared-sqs ✅ (3/3) Yandex ⚠️ not supported" -echo "╚══════════════════════════════════════════════════════════════════════╝" diff --git a/tests/compare_sqs.sh b/tests/compare_sqs.sh deleted file mode 100644 index bcef78f..0000000 --- a/tests/compare_sqs.sh +++ /dev/null @@ -1,272 +0,0 @@ -#!/bin/bash -# compare_sqs.sh — сравнительный бенчмарк Yandex MQ vs shared-sqs -# Запуск из ЛОКАЛИ для равных условий -# Created: 2026-04-11 - -set -uo pipefail - -# ═══ CREDENTIALS ═══ -# Yandex MQ -Y_AK="YCAJEQDz_Eg_i4C4M7TAen2fd" -Y_SK="YCMDfD8OKFK51knPyydwQOYts7Q81_3YBhv4sd_j" -Y_REGION="ru-central1" -Y_ENDPOINT="https://message-queue.api.cloud.yandex.net" -Y_QURL="https://message-queue.api.cloud.yandex.net/b1gatc4m3hv1ldldhljp/dj60000000k2jb2o02mt/foropus" - -# Our SQS -O_AK="SSAK-ed0b0c64dcc135adad9e11be" -O_SK="f917c133e4ad74cf37c8e3ba29d74a39f42f117a202bd672436c92fdb6b6f3dc" -O_REGION="us-east-1" -O_ENDPOINT="https://qu.kube5s.ru" -O_QURL="https://qu.kube5s.ru/t-e0ce25e83be94c58/foropus-test" - -# ═══ ПАРАМЕТРЫ ═══ -SEQ_COUNT=20 # последовательных send/receive -PAR_WORKERS=10 # параллельных воркеров -PAR_MSGS=5 # сообщений на воркер (итого 50) -BURST=30 # burst одновременно - -TMPDIR_CMP=$(mktemp -d /tmp/sqs_compare_XXXXX) -trap "rm -rf $TMPDIR_CMP" EXIT - -# ═══ ХЕЛПЕРЫ ═══ -ysqs() { - AWS_ACCESS_KEY_ID="$Y_AK" AWS_SECRET_ACCESS_KEY="$Y_SK" \ - AWS_DEFAULT_REGION="$Y_REGION" \ - aws --endpoint-url "$Y_ENDPOINT" --output json sqs "$@" 2>&1 -} - -osqs() { - AWS_ACCESS_KEY_ID="$O_AK" AWS_SECRET_ACCESS_KEY="$O_SK" \ - AWS_DEFAULT_REGION="$O_REGION" \ - aws --endpoint-url "$O_ENDPOINT" --output json sqs "$@" 2>&1 -} - -# ms_since — миллисекунды с момента $1 -ms_now() { date +%s%3N; } - -echo "╔═══════════════════════════════════════════════════════════════════╗" -echo "║ BENCHMARK: Yandex MQ vs shared-sqs — $(date '+%Y-%m-%d %H:%M:%S') ║" -echo "║ Из локали, равные условия ║" -echo "╚═══════════════════════════════════════════════════════════════════╝" -echo "" - -# ═══════════════════════════════════════ -# 1. SEQUENTIAL SEND — latency per message -# ═══════════════════════════════════════ -echo "═══ 1. Sequential SendMessage (${SEQ_COUNT} msgs) ═══" - -# Yandex -echo -n " Yandex: " -Y_START=$(ms_now) -Y_SEND_OK=0 -for i in $(seq 1 $SEQ_COUNT); do - R=$(ysqs send-message --queue-url "$Y_QURL" --message-body "bench-seq-${i}") - if echo "$R" | grep -q "MessageId"; then Y_SEND_OK=$((Y_SEND_OK+1)); fi - echo -n "." -done -Y_SEND_MS=$(( $(ms_now) - Y_START )) -Y_SEND_AVG=$(( Y_SEND_MS / SEQ_COUNT )) -echo " ${Y_SEND_OK}/${SEQ_COUNT} ok, ${Y_SEND_MS}ms total, avg=${Y_SEND_AVG}ms" - -# Ours -echo -n " Ours: " -O_START=$(ms_now) -O_SEND_OK=0 -for i in $(seq 1 $SEQ_COUNT); do - R=$(osqs send-message --queue-url "$O_QURL" --message-body "bench-seq-${i}") - if echo "$R" | grep -q "MessageId"; then O_SEND_OK=$((O_SEND_OK+1)); fi - echo -n "." -done -O_SEND_MS=$(( $(ms_now) - O_START )) -O_SEND_AVG=$(( O_SEND_MS / SEQ_COUNT )) -echo " ${O_SEND_OK}/${SEQ_COUNT} ok, ${O_SEND_MS}ms total, avg=${O_SEND_AVG}ms" -echo "" - -# ═══════════════════════════════════════ -# 2. SEQUENTIAL RECEIVE — latency per message -# ═══════════════════════════════════════ -echo "═══ 2. Sequential ReceiveMessage (${SEQ_COUNT} msgs) ═══" - -# Yandex -echo -n " Yandex: " -Y_START=$(ms_now) -Y_RECV_OK=0 -for i in $(seq 1 $SEQ_COUNT); do - R=$(ysqs receive-message --queue-url "$Y_QURL" --max-number-of-messages 1 --wait-time-seconds 2) - if echo "$R" | grep -q "MessageId"; then - Y_RECV_OK=$((Y_RECV_OK+1)) - H=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+') - ysqs delete-message --queue-url "$Y_QURL" --receipt-handle "$H" >/dev/null 2>&1 - fi - echo -n "." -done -Y_RECV_MS=$(( $(ms_now) - Y_START )) -Y_RECV_AVG=$(( Y_RECV_MS / SEQ_COUNT )) -echo " ${Y_RECV_OK}/${SEQ_COUNT} ok, ${Y_RECV_MS}ms total, avg=${Y_RECV_AVG}ms" - -# Ours -echo -n " Ours: " -O_START=$(ms_now) -O_RECV_OK=0 -for i in $(seq 1 $SEQ_COUNT); do - R=$(osqs receive-message --queue-url "$O_QURL" --max-number-of-messages 1 --wait-time-seconds 2) - if echo "$R" | grep -q "MessageId"; then - O_RECV_OK=$((O_RECV_OK+1)) - H=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+') - osqs delete-message --queue-url "$O_QURL" --receipt-handle "$H" >/dev/null 2>&1 - fi - echo -n "." -done -O_RECV_MS=$(( $(ms_now) - O_START )) -O_RECV_AVG=$(( O_RECV_MS / SEQ_COUNT )) -echo " ${O_RECV_OK}/${SEQ_COUNT} ok, ${O_RECV_MS}ms total, avg=${O_RECV_AVG}ms" -echo "" - -# ═══════════════════════════════════════ -# 3. PARALLEL SEND — throughput -# ═══════════════════════════════════════ -TOTAL_PAR=$((PAR_WORKERS * PAR_MSGS)) -echo "═══ 3. Parallel SendMessage (${PAR_WORKERS} workers × ${PAR_MSGS} = ${TOTAL_PAR} msgs) ═══" - -# Yandex -echo -n " Yandex: " -Y_START=$(ms_now) -for w in $(seq 1 $PAR_WORKERS); do - ( - ok=0 - for m in $(seq 1 $PAR_MSGS); do - R=$(ysqs send-message --queue-url "$Y_QURL" --message-body "par-${w}-${m}") - if echo "$R" | grep -q "MessageId"; then ok=$((ok+1)); fi - done - echo "$ok" > "${TMPDIR_CMP}/y_par_${w}" - ) & -done -wait -Y_PAR_MS=$(( $(ms_now) - Y_START )) -Y_PAR_OK=0 -for w in $(seq 1 $PAR_WORKERS); do - [[ -f "${TMPDIR_CMP}/y_par_${w}" ]] && Y_PAR_OK=$((Y_PAR_OK + $(cat "${TMPDIR_CMP}/y_par_${w}"))) -done -Y_PAR_RPS=$(( Y_PAR_OK * 1000 / Y_PAR_MS )) -echo "${Y_PAR_OK}/${TOTAL_PAR} ok, ${Y_PAR_MS}ms, ~${Y_PAR_RPS} msg/sec" - -# Ours -echo -n " Ours: " -O_START=$(ms_now) -for w in $(seq 1 $PAR_WORKERS); do - ( - ok=0 - for m in $(seq 1 $PAR_MSGS); do - R=$(osqs send-message --queue-url "$O_QURL" --message-body "par-${w}-${m}") - if echo "$R" | grep -q "MessageId"; then ok=$((ok+1)); fi - done - echo "$ok" > "${TMPDIR_CMP}/o_par_${w}" - ) & -done -wait -O_PAR_MS=$(( $(ms_now) - O_START )) -O_PAR_OK=0 -for w in $(seq 1 $PAR_WORKERS); do - [[ -f "${TMPDIR_CMP}/o_par_${w}" ]] && O_PAR_OK=$((O_PAR_OK + $(cat "${TMPDIR_CMP}/o_par_${w}"))) -done -O_PAR_RPS=$(( O_PAR_OK * 1000 / O_PAR_MS )) -echo "${O_PAR_OK}/${TOTAL_PAR} ok, ${O_PAR_MS}ms, ~${O_PAR_RPS} msg/sec" -echo "" - -# ═══════════════════════════════════════ -# 4. BURST — все сразу -# ═══════════════════════════════════════ -echo "═══ 4. Burst SendMessage (${BURST} одновременно) ═══" - -# Yandex -echo -n " Yandex: " -Y_START=$(ms_now) -for b in $(seq 1 $BURST); do - ( R=$(ysqs send-message --queue-url "$Y_QURL" --message-body "burst-${b}") - echo "$R" | grep -q "MessageId" && echo 1 || echo 0 - ) > "${TMPDIR_CMP}/y_burst_${b}" & -done -wait -Y_BURST_MS=$(( $(ms_now) - Y_START )) -Y_BURST_OK=0 -for b in $(seq 1 $BURST); do - [[ -f "${TMPDIR_CMP}/y_burst_${b}" ]] && Y_BURST_OK=$((Y_BURST_OK + $(cat "${TMPDIR_CMP}/y_burst_${b}"))) -done -echo "${Y_BURST_OK}/${BURST} ok, ${Y_BURST_MS}ms" - -# Ours -echo -n " Ours: " -O_START=$(ms_now) -for b in $(seq 1 $BURST); do - ( R=$(osqs send-message --queue-url "$O_QURL" --message-body "burst-${b}") - echo "$R" | grep -q "MessageId" && echo 1 || echo 0 - ) > "${TMPDIR_CMP}/o_burst_${b}" & -done -wait -O_BURST_MS=$(( $(ms_now) - O_START )) -O_BURST_OK=0 -for b in $(seq 1 $BURST); do - [[ -f "${TMPDIR_CMP}/o_burst_${b}" ]] && O_BURST_OK=$((O_BURST_OK + $(cat "${TMPDIR_CMP}/o_burst_${b}"))) -done -echo "${O_BURST_OK}/${BURST} ok, ${O_BURST_MS}ms" -echo "" - -# ═══════════════════════════════════════ -# 5. GetQueueAttributes — latency -# ═══════════════════════════════════════ -echo "═══ 5. GetQueueAttributes (5 раз) ═══" - -echo -n " Yandex: " -Y_START=$(ms_now) -for i in $(seq 1 5); do - ysqs get-queue-attributes --queue-url "$Y_QURL" --attribute-names ApproximateNumberOfMessages >/dev/null 2>&1 - echo -n "." -done -Y_ATTR_MS=$(( $(ms_now) - Y_START )) -Y_ATTR_AVG=$(( Y_ATTR_MS / 5 )) -echo " ${Y_ATTR_MS}ms total, avg=${Y_ATTR_AVG}ms" - -echo -n " Ours: " -O_START=$(ms_now) -for i in $(seq 1 5); do - osqs get-queue-attributes --queue-url "$O_QURL" --attribute-names ApproximateNumberOfMessages >/dev/null 2>&1 - echo -n "." -done -O_ATTR_MS=$(( $(ms_now) - O_START )) -O_ATTR_AVG=$(( O_ATTR_MS / 5 )) -echo " ${O_ATTR_MS}ms total, avg=${O_ATTR_AVG}ms" -echo "" - -# ═══════════════════════════════════════ -# 6. PURGE — очистка после тестов -# ═══════════════════════════════════════ -echo "═══ 6. Purge (очистка) ═══" -ysqs purge-queue --queue-url "$Y_QURL" >/dev/null 2>&1 && echo " Yandex: purged" || echo " Yandex: purge failed (cooldown?)" -osqs purge-queue --queue-url "$O_QURL" >/dev/null 2>&1 && echo " Ours: purged" || echo " Ours: purge failed" -echo "" - -# ═══════════════════════════════════════ -# ИТОГОВАЯ ТАБЛИЦА -# ═══════════════════════════════════════ -echo "╔══════════════════════════════════════════════════════════════════════════╗" -echo "║ РЕЗУЛЬТАТЫ СРАВНЕНИЯ ║" -echo "╠══════════════════════════╦══════════════════╦══════════════════╦════════╣" -echo "║ Тест ║ Yandex MQ ║ shared-sqs ║ Winner ║" -echo "╠══════════════════════════╬══════════════════╬══════════════════╬════════╣" -printf "║ %-24s ║ %4dms avg %2d/%2d ║ %4dms avg %2d/%2d ║ %-6s ║\n" \ - "Seq Send (${SEQ_COUNT}msg)" "$Y_SEND_AVG" "$Y_SEND_OK" "$SEQ_COUNT" "$O_SEND_AVG" "$O_SEND_OK" "$SEQ_COUNT" \ - "$([ $Y_SEND_AVG -le $O_SEND_AVG ] && echo 'YMQ' || echo 'OURS')" -printf "║ %-24s ║ %4dms avg %2d/%2d ║ %4dms avg %2d/%2d ║ %-6s ║\n" \ - "Seq Recv+Del (${SEQ_COUNT}msg)" "$Y_RECV_AVG" "$Y_RECV_OK" "$SEQ_COUNT" "$O_RECV_AVG" "$O_RECV_OK" "$SEQ_COUNT" \ - "$([ $Y_RECV_AVG -le $O_RECV_AVG ] && echo 'YMQ' || echo 'OURS')" -printf "║ %-24s ║ %4dms %3d msg/s ║ %4dms %3d msg/s ║ %-6s ║\n" \ - "Parallel Send (${TOTAL_PAR}msg)" "$Y_PAR_MS" "$Y_PAR_RPS" "$O_PAR_MS" "$O_PAR_RPS" \ - "$([ $Y_PAR_RPS -ge $O_PAR_RPS ] && echo 'YMQ' || echo 'OURS')" -printf "║ %-24s ║ %4dms %2d/%2d ║ %4dms %2d/%2d ║ %-6s ║\n" \ - "Burst (${BURST} simultaneous)" "$Y_BURST_MS" "$Y_BURST_OK" "$BURST" "$O_BURST_MS" "$O_BURST_OK" "$BURST" \ - "$([ $Y_BURST_MS -le $O_BURST_MS ] && echo 'YMQ' || echo 'OURS')" -printf "║ %-24s ║ %4dms avg ║ %4dms avg ║ %-6s ║\n" \ - "GetQueueAttributes (5x)" "$Y_ATTR_AVG" "$O_ATTR_AVG" \ - "$([ $Y_ATTR_AVG -le $O_ATTR_AVG ] && echo 'YMQ' || echo 'OURS')" -echo "╚══════════════════════════╩══════════════════╩══════════════════╩════════╝" diff --git a/tests/hardcore_test.sh b/tests/hardcore_test.sh deleted file mode 100644 index 11043af..0000000 --- a/tests/hardcore_test.sh +++ /dev/null @@ -1,1169 +0,0 @@ -#!/bin/bash -# hardcore_test.sh — расширенный тест shared-sqs -# Created: 2026-04-09 -# Updated: 2026-04-11 — добавлены TagQueue/UntagQueue/ListQueueTags/ChangeMessageVisibilityBatch, -# UI API с JWT авторизацией (nubes api_token). -# Покрывает: все 17 SQS команд, Admin API, AWS CLI + awscurl, кросс-доставка, -# изоляция тенантов, невалидный ввод, batch, лимиты, UI API с JWT. -# Запуск: BASE_URL=https://qu.kube5s.ru ADMIN_TOKEN=... bash tests/hardcore_test.sh - -set -uo pipefail - -# ═══════════════════════════════════════════ -# КОНФИГУРАЦИЯ -# ═══════════════════════════════════════════ -BASE_URL="${BASE_URL:-https://qu.kube5s.ru}" -ADMIN_TOKEN="${ADMIN_TOKEN:-sqs-admin-7a7d8bd0c060a75c198d48680f34077a}" -AWSCURL="${AWSCURL:-/home/naeel/.local/bin/awscurl}" -# JWT-токен для авторизации в UI API (/ui/api/*); получается через nubes.ru → api_token -NUBES_JWT="${NUBES_JWT:-eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoLWFwaSIsInN1YiI6IjAxOTllMzI1LTFjZGYtN2NkYS05MzE5LWU1MzAyYTg1ZTI5MSIsImV4cCI6MTc4NjkzMjI2MCwiaWF0IjoxNzcxMzgwMjYwLCJqdGkiOiIzOTQ3ZTgyMy0yNjljLTQ0MTAtYmU0My1iNGVkNTc1Njg0ZTQiLCJhdXRoX3RpbWUiOjAsInR5cCI6IiIsImF6cCI6IiIsInNlc3Npb25fc3RhdGUiOiIiLCJhY3IiOiIiLCJhbGxvd2VkLW9yaWdpbnMiOm51bGwsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6bnVsbH0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpudWxsfX0sInNjb3BlIjoiIiwic2lkIjoiIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiIiwiQ2xpZW50SUQiOiIiLCJncm91cHMiOm51bGwsInByZWZlcnJlZF91c2VybmFtZSI6IiIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImVtYWlsIjoidGF6ZXRAbmFyb2QucnUifQ.hzpIIqNWkKIoYUXDaLY7DLyGKH70rz0ZTqanv19qxF10i3N1t1g_KknA4Qsw1MduTyLzIz7y5SRSr4PSQ1gzR0vB_C0GudSFUhyBNNKkS4ClhRDWW9eN_IIEljbiJMLQi2L07XJ7Y5DQ0sIHRAPLkreCDFMKQ0yTCrKoScCJIDuUqzaTcOaX-hfjaxW8iV0SZMDxl0C5O3tke0btxkaLBaAcWH0V-1yu2r2m29fyU33FqikF0xAcDXiuZphfsrShKQYArZjKAphYCP_Vpmr-1sdjinkn8sPSk1qZny0rka8G6WVZUGaZSOnW8SYNLVUwdqtuQmK-Y18o7U0Suzrsjg}" -TS=$(date +%s) - -PASS=0 -FAIL=0 -TOTAL=0 - -# ═══════════════════════════════════════════ -# ХЕЛПЕРЫ -# ═══════════════════════════════════════════ - -# check — ожидается что вывод содержит паттерн -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 -10)" - fi -} - -# check_not — ожидается что вывод НЕ содержит паттерн -check_not() { - local name="$1" output="$2" pattern="$3" - TOTAL=$((TOTAL + 1)) - if echo "$output" | grep -qE "$pattern"; then - FAIL=$((FAIL + 1)) - echo " ❌ $name (содержит '$pattern')" - echo " получено: $(echo "$output" | head -10)" - else - PASS=$((PASS + 1)) - echo " ✅ $name" - fi -} - -# check_eq — проверка числа -check_eq() { - local name="$1" got="$2" want="$3" - TOTAL=$((TOTAL + 1)) - if [ "$got" -eq "$want" ]; then - PASS=$((PASS + 1)) - echo " ✅ $name ($got)" - else - FAIL=$((FAIL + 1)) - echo " ❌ $name: ожидалось=$want, получено=$got" - fi -} - -# sqs — AWS CLI враппер с credentials тенанта -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 -} - -# ui_jwt — UI API запрос с JWT авторизацией -ui_jwt() { - local method="$1" path="$2" body="${3:-}" - if [[ -n "$body" ]]; then - curl -s --max-time 15 -X "$method" \ - -H "Authorization: Bearer $NUBES_JWT" \ - -H "Content-Type: application/json" \ - -d "$body" "${BASE_URL}/ui/api${path}" 2>&1 - else - curl -s --max-time 15 -X "$method" \ - -H "Authorization: Bearer $NUBES_JWT" \ - "${BASE_URL}/ui/api${path}" 2>&1 - fi -} - -# acurl — awscurl враппер для SQS Query Protocol -# $1=ak, $2=sk, остальное — form-encoded body (Action=...&...) -acurl() { - local ak="$1" sk="$2" - shift 2 - "$AWSCURL" \ - --region us-east-1 --service sqs \ - --access_key "$ak" --secret_key "$sk" \ - -X POST \ - -H "Content-Type: application/x-www-form-urlencoded" \ - -d "$*" \ - "$BASE_URL/" 2>&1 -} - -# admin_api — Admin REST API запрос через curl -# $1=method, $2=path, $3=body (опц.) -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 -} - -# qurl — строит HTTPS Queue URL из TenantID + имени очереди -qurl() { - echo "${BASE_URL}/${1}/${2}" -} - -# xml_field — извлекает значение из XML тега -xml_field() { - echo "$1" | grep -oP "(?<=<${2}>)[^<]+" | head -1 -} - -echo "╔══════════════════════════════════════════════════════════╗" -echo "║ shared-sqs Hardcore Test — $(date '+%Y-%m-%d %H:%M:%S') ║" -echo "║ Endpoint: ${BASE_URL} ║" -echo "║ Методы: AWS CLI + awscurl + curl (Admin) ║" -echo "╚══════════════════════════════════════════════════════════╝" -echo "" - -# Проверяем зависимости -for dep in aws curl jq "$AWSCURL"; do - if ! command -v "$dep" &>/dev/null; then - echo "❌ Не найдено: $dep — прерываем" - exit 2 - fi -done - -# ═══════════════════════════════════════════ -echo "── 1. Admin API — health + auth + CRUD ──" -# ═══════════════════════════════════════════ - -# health без токена — должен пройти -R=$(curl -s --max-time 10 "${BASE_URL}/health" 2>&1) -check "health доступен без токена" "$R" "ok|OK|healthy|status" - -# admin без токена — 401/403 -R=$(curl -s --max-time 10 -X GET "${BASE_URL}/admin/tenants" 2>&1) -check "GET /admin/tenants без токена → 401/403" "$R" "Unauthorized|Forbidden|401|403|unauthorized" - -# admin с неверным токеном — 401/403 -R=$(curl -s --max-time 10 -X GET "${BASE_URL}/admin/tenants" \ - -H "Authorization: Bearer wrong-token-xyz" 2>&1) -check "GET /admin/tenants c неверным токеном → 401/403" "$R" "Unauthorized|Forbidden|401|403|unauthorized" - -# создать тенанта T1 -R=$(admin_api POST /admin/tenants '{"name":"hc-t1-'"$TS"'"}') -check "POST /admin/tenants → создаёт T1" "$R" "access_key|id" -T1_ID=$(echo "$R" | jq -r '.id' 2>/dev/null) -T1_AK=$(echo "$R" | jq -r '.access_key' 2>/dev/null) -T1_SK=$(echo "$R" | jq -r '.secret_key' 2>/dev/null) - -# создать тенанта T2 -R=$(admin_api POST /admin/tenants '{"name":"hc-t2-'"$TS"'"}') -check "POST /admin/tenants → создаёт T2" "$R" "access_key|id" -T2_ID=$(echo "$R" | jq -r '.id' 2>/dev/null) -T2_AK=$(echo "$R" | jq -r '.access_key' 2>/dev/null) -T2_SK=$(echo "$R" | jq -r '.secret_key' 2>/dev/null) - -# список тенантов — видим обоих -R=$(admin_api GET /admin/tenants) -check "GET /admin/tenants — T1 виден" "$R" "hc-t1-$TS" -check "GET /admin/tenants — T2 виден" "$R" "hc-t2-$TS" - -# GET конкретного тенанта -R=$(admin_api GET "/admin/tenants/${T1_ID}") -check "GET /admin/tenants/{id} — возвращает T1" "$R" "$T1_AK" - -echo "" - -# ═══════════════════════════════════════════ -echo "── 2. SQS CRUD через AWS CLI (T1) ──" -# ═══════════════════════════════════════════ - -CLI_Q="hc-cli-${TS}" - -# создание -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$CLI_Q") -check "CLI: create-queue" "$R" "QueueUrl" - -# список — очередь видна -R=$(sqs "$T1_AK" "$T1_SK" list-queues) -check "CLI: list-queues содержит очередь" "$R" "$CLI_Q" - -# get-queue-url -R=$(sqs "$T1_AK" "$T1_SK" get-queue-url --queue-name "$CLI_Q") -check "CLI: get-queue-url" "$R" "QueueUrl" - -# send-message -CQURL=$(qurl "$T1_ID" "$CLI_Q") -R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$CQURL" --message-body "cli-hello-${TS}") -check "CLI: send-message → MessageId" "$R" "MessageId" -check "CLI: send-message → MD5" "$R" "MD5OfMessageBody" - -# receive-message -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$CQURL") -check "CLI: receive-message → тело" "$R" "cli-hello-${TS}" -RECEIPT=$(echo "$R" | jq -r '.Messages[0].ReceiptHandle' 2>/dev/null) - -# delete-message — немедленно после receive, без промежуточных операций -if [ -n "$RECEIPT" ] && [ "$RECEIPT" != "null" ]; then - R=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$CQURL" --receipt-handle "$RECEIPT" 2>&1 && echo "OK" || echo "FAIL") - check "CLI: delete-message" "$R" "OK" -else - TOTAL=$((TOTAL + 1)); FAIL=$((FAIL + 1)) - echo " ❌ CLI: delete-message (нет ReceiptHandle)" -fi - -# get-queue-attributes -R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes --queue-url "$CQURL" --attribute-names All) -check "CLI: get-queue-attributes" "$R" "Attributes|ApproximateNumberOfMessages" - -# set-queue-attributes visibility -R=$(sqs "$T1_AK" "$T1_SK" set-queue-attributes --queue-url "$CQURL" --attributes '{"VisibilityTimeout":"45"}' 2>&1 && echo "OK" || echo "FAIL") -check "CLI: set-queue-attributes VisibilityTimeout" "$R" "OK" - -# purge -sqs "$T1_AK" "$T1_SK" send-message --queue-url "$CQURL" --message-body "purge-me" >/dev/null 2>&1 || true -R=$(sqs "$T1_AK" "$T1_SK" purge-queue --queue-url "$CQURL" 2>&1 && echo "OK" || echo "FAIL") -check "CLI: purge-queue" "$R" "OK" - -# delete-queue -R=$(sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$CQURL" 2>&1 && echo "OK" || echo "FAIL") -check "CLI: delete-queue" "$R" "OK" - -# очередь исчезла из списка -R=$(sqs "$T1_AK" "$T1_SK" list-queues 2>&1) -check_not "CLI: deleted queue не в списке" "$R" "$CLI_Q" - -echo "" - -# ═══════════════════════════════════════════ -echo "── 3. SQS CRUD через awscurl (T2) ──" -# ═══════════════════════════════════════════ - -AC_Q="hc-ac-${TS}" -AC_QURL="${BASE_URL}/${T2_ID}/${AC_Q}" -VER="Version=2012-11-05" - -# create-queue -R=$(acurl "$T2_AK" "$T2_SK" "Action=CreateQueue&QueueName=${AC_Q}&${VER}") -check "awscurl: CreateQueue" "$R" "CreateQueueResult" - -# send-message -R=$(acurl "$T2_AK" "$T2_SK" "Action=SendMessage&QueueUrl=$(python3 -c "import urllib.parse;print(urllib.parse.quote('${AC_QURL}'))")&MessageBody=acurl-hello-${TS}&${VER}") -check "awscurl: SendMessage → MessageId" "$R" "MessageId" -check "awscurl: SendMessage → MD5" "$R" "MD5OfMessageBody" - -# receive-message -R=$(acurl "$T2_AK" "$T2_SK" "Action=ReceiveMessage&QueueUrl=$(python3 -c "import urllib.parse;print(urllib.parse.quote('${AC_QURL}'))")&${VER}") -check "awscurl: ReceiveMessage → тело" "$R" "acurl-hello-${TS}" -AC_RECEIPT=$(xml_field "$R" "ReceiptHandle") - -# delete-message -if [ -n "$AC_RECEIPT" ]; then - ENC_RECEIPT=$(python3 -c "import urllib.parse,sys;print(urllib.parse.quote(sys.argv[1]))" "$AC_RECEIPT") - R=$(acurl "$T2_AK" "$T2_SK" "Action=DeleteMessage&QueueUrl=$(python3 -c "import urllib.parse;print(urllib.parse.quote('${AC_QURL}'))")&ReceiptHandle=${ENC_RECEIPT}&${VER}") - check "awscurl: DeleteMessage" "$R" "DeleteMessageResponse" -else - TOTAL=$((TOTAL + 1)); FAIL=$((FAIL + 1)) - echo " ❌ awscurl: DeleteMessage (нет ReceiptHandle для теста)" -fi - -# purge -acurl "$T2_AK" "$T2_SK" "Action=SendMessage&QueueUrl=$(python3 -c "import urllib.parse;print(urllib.parse.quote('${AC_QURL}'))")&MessageBody=purge-me&${VER}" >/dev/null 2>&1 || true -R=$(acurl "$T2_AK" "$T2_SK" "Action=PurgeQueue&QueueUrl=$(python3 -c "import urllib.parse;print(urllib.parse.quote('${AC_QURL}'))")&${VER}") -check "awscurl: PurgeQueue" "$R" "PurgeQueueResponse" - -# delete-queue -R=$(acurl "$T2_AK" "$T2_SK" "Action=DeleteQueue&QueueUrl=$(python3 -c "import urllib.parse;print(urllib.parse.quote('${AC_QURL}'))")&${VER}") -check "awscurl: DeleteQueue" "$R" "DeleteQueueResponse" - -echo "" - -# ═══════════════════════════════════════════ -echo "── 4. Кросс-доставка: CLI ↔ awscurl ──" -# ═══════════════════════════════════════════ - -XQ="hc-cross-${TS}" -XQURL=$(qurl "$T1_ID" "$XQ") -ENC_XQURL=$(python3 -c "import urllib.parse;print(urllib.parse.quote('${XQURL}'))") - -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$XQ" >/dev/null 2>&1 || true - -# CLI отправляет → awscurl получает -sqs "$T1_AK" "$T1_SK" send-message --queue-url "$XQURL" --message-body "from-cli-${TS}" >/dev/null 2>&1 || true -R=$(acurl "$T1_AK" "$T1_SK" "Action=ReceiveMessage&QueueUrl=${ENC_XQURL}&MaxNumberOfMessages=1&${VER}") -check "CLI→awscurl: сообщение доставлено" "$R" "from-cli-${TS}" - -# Очищаем очередь перед тестом в обратную сторону -acurl "$T1_AK" "$T1_SK" "Action=PurgeQueue&QueueUrl=${ENC_XQURL}&${VER}" >/dev/null 2>&1 || true -sleep 1 - -# awscurl отправляет → CLI получает -acurl "$T1_AK" "$T1_SK" "Action=SendMessage&QueueUrl=${ENC_XQURL}&MessageBody=from-acurl-${TS}&${VER}" >/dev/null 2>&1 || true -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$XQURL" --max-number-of-messages 1) -check "awscurl→CLI: сообщение доставлено" "$R" "from-acurl-${TS}" - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$XQURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 5. Изоляция между тенантами ──" -# ═══════════════════════════════════════════ - -# Создаём одинаковые очереди у T1 и T2 -ISO_Q="hc-iso-${TS}" -ISO_Q1URL=$(qurl "$T1_ID" "$ISO_Q") -ISO_Q2URL=$(qurl "$T2_ID" "$ISO_Q") - -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$ISO_Q" >/dev/null 2>&1 || true -sqs "$T2_AK" "$T2_SK" create-queue --queue-name "$ISO_Q" >/dev/null 2>&1 || true - -# T1 отправляет приватное сообщение -sqs "$T1_AK" "$T1_SK" send-message --queue-url "$ISO_Q1URL" --message-body "private-t1-${TS}" >/dev/null 2>&1 || true -# T2 отправляет в свою очередь с тем же именем -sqs "$T2_AK" "$T2_SK" send-message --queue-url "$ISO_Q2URL" --message-body "private-t2-${TS}" >/dev/null 2>&1 || true - -# T1 получает только своё -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$ISO_Q1URL") -check "Изоляция: T1 получает только T1-сообщение" "$R" "private-t1-${TS}" -check_not "Изоляция: T1 не видит T2-сообщение" "$R" "private-t2-${TS}" - -# T2 получает только своё -R=$(sqs "$T2_AK" "$T2_SK" receive-message --queue-url "$ISO_Q2URL") -check "Изоляция: T2 получает только T2-сообщение" "$R" "private-t2-${TS}" -check_not "Изоляция: T2 не видит T1-сообщение" "$R" "private-t1-${TS}" - -# T1 не может читать очередь T2 (неправильный URL) -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$ISO_Q2URL" 2>&1 || true) -check_not "T1 не может читать очередь T2" "$R" "private-t2-${TS}" - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$ISO_Q1URL" >/dev/null 2>&1 || true -sqs "$T2_AK" "$T2_SK" delete-queue --queue-url "$ISO_Q2URL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 6. Невалидные credentials ──" -# ═══════════════════════════════════════════ - -FAKE_AK="SSAK-ffffffffffffffff" -FAKE_SK="ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" -BAD_Q="hc-bad-${TS}" - -# Неверный ключ — создание провалится -R=$(sqs "$FAKE_AK" "$FAKE_SK" create-queue --queue-name "$BAD_Q" 2>&1 || true) -check "Неверный cred: create блокируется" "$R" "InvalidClientTokenId|Unauthorized|not authorized|could not be authenticated|AuthFailure|403|401" - -# Неверный ключ — отправка провалится -R=$(sqs "$FAKE_AK" "$FAKE_SK" send-message --queue-url "$(qurl 'fake-tenant' $BAD_Q)" --message-body "x" 2>&1 || true) -check "Неверный cred: send блокируется" "$R" "InvalidClientTokenId|Unauthorized|not authorized|could not be authenticated|AuthFailure|403|401" - -# Несуществующая очередь у реального тенанта -R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$(qurl "$T1_ID" "no-such-queue-${TS}")" --message-body "x" 2>&1 || true) -check "Несуществующая очередь" "$R" "NonExistentQueue|AWS.SimpleQueueService.NonExistentQueue|does not exist" - -echo "" - -# ═══════════════════════════════════════════ -echo "── 7. Невалидный Action через awscurl ──" -# ═══════════════════════════════════════════ - -# Неизвестный Action -R=$(acurl "$T1_AK" "$T1_SK" "Action=DoWeirdThing&${VER}") -check "awscurl: неизвестный Action" "$R" "InvalidAction|UnknownOperation|Error|Bad Request" - -# Отсутствует QueueUrl при SendMessage -R=$(acurl "$T1_AK" "$T1_SK" "Action=SendMessage&MessageBody=x&${VER}") -check "awscurl: SendMessage без QueueUrl" "$R" "QueueUrl|missing|Error|MissingParameter" - -echo "" - -# ═══════════════════════════════════════════ -echo "── 8. Спецсимволы в сообщениях ──" -# ═══════════════════════════════════════════ - -SPEC_Q="hc-spec-${TS}" -SPEC_QURL=$(qurl "$T1_ID" "$SPEC_Q") -ENC_SPEC_QURL=$(python3 -c "import urllib.parse;print(urllib.parse.quote('${SPEC_QURL}'))") - -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$SPEC_Q" >/dev/null 2>&1 || true - -# JSON строка -sqs "$T1_AK" "$T1_SK" send-message --queue-url "$SPEC_QURL" \ - --message-body '{"event":"test","count":42,"ok":true}' >/dev/null 2>&1 || true -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$SPEC_QURL") -check "JSON в теле сообщения" "$R" "event" - -# XML спецсимволы через CLI — < > & "' -sqs "$T1_AK" "$T1_SK" send-message --queue-url "$SPEC_QURL" \ - --message-body 'A&B' >/dev/null 2>&1 || true -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$SPEC_QURL") -check "XML спецсимволы в теле" "$R" "root|attr|A" - -# Unicode + emoji через CLI -sqs "$T1_AK" "$T1_SK" send-message --queue-url "$SPEC_QURL" \ - --message-body 'Unicode test Privet mir' >/dev/null 2>&1 || true -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$SPEC_QURL") -check "Unicode тест: сообщение получено" "$R" "Privet mir|Body" - -# Пустая строка через awscurl -R=$(acurl "$T1_AK" "$T1_SK" "Action=SendMessage&QueueUrl=${ENC_SPEC_QURL}&MessageBody=&${VER}") -check "awscurl: пустое тело → ошибка или ok" "$R" "MessageId|EmptyQueueMessage|Error|MissingParameter" - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$SPEC_QURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 9. Visibility Timeout ──" -# ═══════════════════════════════════════════ - -VIS_Q="hc-vis-${TS}" -VIS_QURL=$(qurl "$T1_ID" "$VIS_Q") - -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$VIS_Q" >/dev/null 2>&1 || true -sqs "$T1_AK" "$T1_SK" send-message --queue-url "$VIS_QURL" --message-body "vis-msg-${TS}" >/dev/null 2>&1 || true - -# Получаем с visibility=2 секунды -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$VIS_QURL" --visibility-timeout 2) -check "Visibility: первое получение успешно" "$R" "vis-msg-${TS}" - -# Сразу повторно — сообщение скрыто -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$VIS_QURL") -check_not "Visibility: скрыто сразу после receive" "$R" "vis-msg-${TS}" - -# Ждём истечения visibility window -sleep 3 - -# После паузы — снова доступно (получаем с VT=30 и сохраняем receipt для CMV) -R3=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$VIS_QURL" --visibility-timeout 30) -check "Visibility: сообщение снова доступно" "$R3" "vis-msg-${TS}" - -# ChangeMessageVisibility — продлить время скрытия ДО истечения -VIS_RECEIPT=$(echo "$R3" | jq -r '.Messages[0].ReceiptHandle' 2>/dev/null) -if [ -n "$VIS_RECEIPT" ] && [ "$VIS_RECEIPT" != "null" ]; then - R=$(sqs "$T1_AK" "$T1_SK" change-message-visibility \ - --queue-url "$VIS_QURL" \ - --receipt-handle "$VIS_RECEIPT" \ - --visibility-timeout 60 2>&1 && echo "OK" || echo "FAIL") - check "ChangeMessageVisibility: продлеваем скрытие" "$R" "OK" -else - TOTAL=$((TOTAL + 1)); FAIL=$((FAIL + 1)) - echo " ❌ ChangeMessageVisibility (сообщение недоступно для теста)" -fi - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$VIS_QURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 10. Batch операции ──" -# ═══════════════════════════════════════════ - -BQ="hc-batch-${TS}" -BQURL=$(qurl "$T1_ID" "$BQ") -ENC_BQURL=$(python3 -c "import urllib.parse;print(urllib.parse.quote('${BQURL}'))") - -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BQ" >/dev/null 2>&1 || true - -# SendMessageBatch через CLI — 3 сообщения -R=$(sqs "$T1_AK" "$T1_SK" send-message-batch \ - --queue-url "$BQURL" \ - --entries '[{"Id":"b1","MessageBody":"batch-one"},{"Id":"b2","MessageBody":"batch-two"},{"Id":"b3","MessageBody":"batch-three"}]') -check "CLI: SendMessageBatch → Successful" "$R" "Successful" - -# SendMessageBatch через awscurl -R=$(acurl "$T1_AK" "$T1_SK" \ - "Action=SendMessageBatch&QueueUrl=${ENC_BQURL}\ -&SendMessageBatchRequestEntry.1.Id=ac1\ -&SendMessageBatchRequestEntry.1.MessageBody=acurl-batch-1\ -&SendMessageBatchRequestEntry.2.Id=ac2\ -&SendMessageBatchRequestEntry.2.MessageBody=acurl-batch-2\ -&${VER}") -check "awscurl: SendMessageBatch" "$R" "SendMessageBatchResult|MessageId" - -# Получаем несколько -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$BQURL" --max-number-of-messages 5) -MSG_COUNT=$(echo "$R" | jq '.Messages | length' 2>/dev/null || echo "0") -TOTAL=$((TOTAL + 1)) -if [ "$MSG_COUNT" -ge 2 ]; then - PASS=$((PASS + 1)); echo " ✅ Batch: получено $MSG_COUNT сообщений (≥2)" -else - FAIL=$((FAIL + 1)); echo " ❌ Batch: получено $MSG_COUNT/≥2" -fi - -# DeleteMessageBatch -RECEIPTS=$(echo "$R" | jq -r '.Messages[]?.ReceiptHandle' 2>/dev/null | head -2) -if [ -n "$RECEIPTS" ]; then - IDX=1 - BATCH_ENTRIES="[" - while IFS= read -r rh; do - [ "$IDX" -gt 1 ] && BATCH_ENTRIES+="," - # экранируем # в ReceiptHandle для JSON - BATCH_ENTRIES+="{\"Id\":\"d${IDX}\",\"ReceiptHandle\":\"${rh}\"}" - IDX=$((IDX + 1)) - done <<< "$RECEIPTS" - BATCH_ENTRIES+="]" - R=$(sqs "$T1_AK" "$T1_SK" delete-message-batch --queue-url "$BQURL" --entries "$BATCH_ENTRIES" 2>&1 || echo "FAIL") - check "CLI: DeleteMessageBatch" "$R" "Successful|FAIL" -else - TOTAL=$((TOTAL + 1)); FAIL=$((FAIL + 1)) - echo " ❌ DeleteMessageBatch (нет сообщений для теста)" -fi - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$BQURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 10б. TagQueue / UntagQueue / ListQueueTags ──" -# ═══════════════════════════════════════════ - -TAG_Q="hc-tag-${TS}" -TAG_QURL=$(qurl "$T1_ID" "$TAG_Q") -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$TAG_Q" >/dev/null 2>&1 || true - -# TagQueue — добавить теги -R=$(sqs "$T1_AK" "$T1_SK" tag-queue --queue-url "$TAG_QURL" --tags env=prod,team=backend 2>&1 && echo "OK" || echo "FAIL") -check "TagQueue: добавить теги → OK" "$R" "OK" - -# ListQueueTags — теги видны -R=$(sqs "$T1_AK" "$T1_SK" list-queue-tags --queue-url "$TAG_QURL") -check "ListQueueTags: env=prod видна" "$R" "env|prod" -check "ListQueueTags: team=backend видна" "$R" "team|backend" - -# TagQueue — обновить существующий тег (merge) -R=$(sqs "$T1_AK" "$T1_SK" tag-queue --queue-url "$TAG_QURL" --tags env=staging 2>&1 && echo "OK" || echo "FAIL") -check "TagQueue: обновить тег env → OK" "$R" "OK" - -R=$(sqs "$T1_AK" "$T1_SK" list-queue-tags --queue-url "$TAG_QURL") -check "ListQueueTags: env обновлён до staging" "$R" "staging" -check "ListQueueTags: team не удалён при обновлении" "$R" "backend" - -# UntagQueue — удалить один тег -R=$(sqs "$T1_AK" "$T1_SK" untag-queue --queue-url "$TAG_QURL" --tag-keys env 2>&1 && echo "OK" || echo "FAIL") -check "UntagQueue: удалить тег env → OK" "$R" "OK" - -R=$(sqs "$T1_AK" "$T1_SK" list-queue-tags --queue-url "$TAG_QURL") -check_not "ListQueueTags: env удалён" "$R" '"env"' -check "ListQueueTags: team остался" "$R" "backend" - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$TAG_QURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 10в. ChangeMessageVisibilityBatch ──" -# ═══════════════════════════════════════════ - -CMVB_Q="hc-cmvb-${TS}" -CMVB_QURL=$(qurl "$T1_ID" "$CMVB_Q") -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$CMVB_Q" >/dev/null 2>&1 || true - -# Отправляем 3 сообщения -for i in 1 2 3; do - sqs "$T1_AK" "$T1_SK" send-message --queue-url "$CMVB_QURL" --message-body "cmvb-msg-${i}" >/dev/null 2>&1 || true -done - -# Получаем все 3 (visibility=60 чтобы не конкурировали) -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$CMVB_QURL" --max-number-of-messages 3 --visibility-timeout 60) -CMVB_RECEIPTS=$(echo "$R" | jq -r '.Messages[]?.ReceiptHandle' 2>/dev/null || true) - -if [[ -n "$CMVB_RECEIPTS" ]]; then - CMVB_ENTRIES=$(echo "$CMVB_RECEIPTS" | python3 -c " -import sys -entries = [] -for i, rh in enumerate(sys.stdin.read().strip().split('\n'), 1): - if rh: - entries.append('{\"Id\":\"e%d\",\"ReceiptHandle\":\"%s\",\"VisibilityTimeout\":120}' % (i, rh)) -print('[' + ','.join(entries) + ']') -" 2>/dev/null || echo "[]") - if [[ "$CMVB_ENTRIES" != "[]" ]]; then - R=$(sqs "$T1_AK" "$T1_SK" change-message-visibility-batch \ - --queue-url "$CMVB_QURL" \ - --entries "$CMVB_ENTRIES") - check "ChangeMessageVisibilityBatch → Successful" "$R" "Successful" - check_not "ChangeMessageVisibilityBatch → нет Failed" "$R" '"Failed"' - fi -else - TOTAL=$((TOTAL + 1)); FAIL=$((FAIL + 1)) - echo " ❌ ChangeMessageVisibilityBatch (нет сообщений для теста)" -fi - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$CMVB_QURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 11. Лимит очередей (max_queues=2) ──" -# ═══════════════════════════════════════════ - -R=$(admin_api POST /admin/tenants '{"name":"hc-limit-'"$TS"'","max_queues":2}') -check "Создаём тенанта с max_queues=2" "$R" "access_key|id" -LIM_ID=$(echo "$R" | jq -r '.id' 2>/dev/null) -LIM_AK=$(echo "$R" | jq -r '.access_key' 2>/dev/null) -LIM_SK=$(echo "$R" | jq -r '.secret_key' 2>/dev/null) - -# Создаём 2 очереди — должно работать -R1=$(sqs "$LIM_AK" "$LIM_SK" create-queue --queue-name "lim-q1-${TS}" 2>&1 || true) -check "Лимит: первая очередь создана" "$R1" "QueueUrl" - -R2=$(sqs "$LIM_AK" "$LIM_SK" create-queue --queue-name "lim-q2-${TS}" 2>&1 || true) -check "Лимит: вторая очередь создана" "$R2" "QueueUrl" - -# Третья очередь — должна быть отклонена -R3=$(sqs "$LIM_AK" "$LIM_SK" create-queue --queue-name "lim-q3-${TS}" 2>&1 || true) -check "Лимит: третья очередь → ошибка" "$R3" "LimitExceeded|limit|TooManyQueues|max_queues|Error" - -echo "" - -# ═══════════════════════════════════════════ -echo "── 12. Нагрузочный мини-тест (50 сообщений) ──" -# ═══════════════════════════════════════════ - -LQ="hc-load-${TS}" -LQURL=$(qurl "$T1_ID" "$LQ") - -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$LQ" >/dev/null 2>&1 || true - -echo " Отправляем 50 сообщений..." -SEND_OK=0; SEND_FAIL=0 -for i in $(seq 1 50); do - R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$LQURL" --message-body "load-msg-${i}-${TS}" 2>&1 || true) - if echo "$R" | grep -q "MessageId"; then - SEND_OK=$((SEND_OK + 1)) - else - SEND_FAIL=$((SEND_FAIL + 1)) - fi -done -check_eq "Нагрузка: отправлено 50/50" "$SEND_OK" 50 - -echo " Получаем все 50 сообщений..." -GOT=0 -for _ in $(seq 1 20); do - R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$LQURL" --max-number-of-messages 10 2>&1 || true) - CNT=$(echo "$R" | jq '.Messages | length' 2>/dev/null || echo "0") - GOT=$((GOT + CNT)) - [ "$CNT" -eq 0 ] && break - [ "$GOT" -ge 50 ] && break -done -TOTAL=$((TOTAL + 1)) -if [ "$GOT" -ge 50 ]; then - PASS=$((PASS + 1)); echo " ✅ Нагрузка: получено $GOT сообщений (≥50)" -else - FAIL=$((FAIL + 1)); echo " ❌ Нагрузка: получено $GOT/50" -fi - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$LQURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 13. MD5 целостность ──" -# ═══════════════════════════════════════════ - -MD5_Q="hc-md5-${TS}" -MD5_QURL=$(qurl "$T1_ID" "$MD5_Q") - -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$MD5_Q" >/dev/null 2>&1 || true - -MSG="integrity-check-${TS}" -EXPECTED_MD5=$(echo -n "$MSG" | md5sum | awk '{print $1}') - -R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MD5_QURL" --message-body "$MSG") -GOT_MD5=$(echo "$R" | jq -r '.MD5OfMessageBody' 2>/dev/null) - -TOTAL=$((TOTAL + 1)) -if [ "$EXPECTED_MD5" = "$GOT_MD5" ]; then - PASS=$((PASS + 1)); echo " ✅ MD5 совпадает ($EXPECTED_MD5)" -else - FAIL=$((FAIL + 1)); echo " ❌ MD5: ожидалось=$EXPECTED_MD5, получено=$GOT_MD5" -fi - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$MD5_QURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 14. Удаление тенанта → очереди недоступны ──" -# ═══════════════════════════════════════════ - -# Создаём тенанта для cleanup теста -R=$(admin_api POST /admin/tenants '{"name":"hc-cleanup-'"$TS"'"}') -CL_ID=$(echo "$R" | jq -r '.id' 2>/dev/null) -CL_AK=$(echo "$R" | jq -r '.access_key' 2>/dev/null) -CL_SK=$(echo "$R" | jq -r '.secret_key' 2>/dev/null) - -CL_Q="hc-cl-q-${TS}" -CL_QURL=$(qurl "$CL_ID" "$CL_Q") - -# Создаём очередь и кладём сообщение -sqs "$CL_AK" "$CL_SK" create-queue --queue-name "$CL_Q" >/dev/null 2>&1 || true -sqs "$CL_AK" "$CL_SK" send-message --queue-url "$CL_QURL" --message-body "should-be-gone" >/dev/null 2>&1 || true - -# Удаляем тенанта -R=$(admin_api DELETE "/admin/tenants/${CL_ID}") -check "DELETE /admin/tenants/{id}" "$R" '^\s*$|deleted|ok|OK|200' - -# Тенант больше не в списке -R=$(admin_api GET /admin/tenants) -check_not "Удалённый тенант не в списке" "$R" "hc-cleanup-${TS}" - -# Очередь тенанта недоступна -R=$(sqs "$CL_AK" "$CL_SK" receive-message --queue-url "$CL_QURL" 2>&1 || true) -check_not "После delete-tenant: очередь недоступна" "$R" "should-be-gone" - -echo "" - -# ═══════════════════════════════════════════ -echo "── 15. Граничные случаи: имена, пустые тела ──" -# ═══════════════════════════════════════════ - -# Очень длинное имя очереди (AWS позволяет до 80 символов) -# 69 q's + "-" + 10 цифр TS = 80 символов (ровно лимит) -LONG_Q=$(printf 'q%.0s' {1..69})"-${TS}" -LONG_QURL=$(qurl "$T1_ID" "$LONG_Q") -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$LONG_Q" 2>&1) -check "Граница: длинное имя очереди (80 сим)" "$R" "Queue" - -# Пустое тело сообщения — AWS SQS требует непустой MessageBody -R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$LONG_QURL" --message-body "" 2>&1) -check "Граница: пустое тело сообщения → ошибка" "$R" "ERROR|Error|error|MissingParameter|Invalid" - -# Receive из пустой очереди -sqs "$T1_AK" "$T1_SK" purge-queue --queue-url "$LONG_QURL" >/dev/null 2>&1 || true -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$LONG_QURL" 2>&1) -check "Граница: receive из пустой очереди (нет ошибки)" "$R" "Messages|^\s*$" - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$LONG_QURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 16. Большие сообщения (близко к 256KB) ──" -# ═══════════════════════════════════════════ - -BIG_Q="hc-big-${TS}" -BIG_QURL=$(qurl "$T1_ID" "$BIG_Q") -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BIG_Q" >/dev/null 2>&1 || true - -# 100KB (в пределах лимита) через JSON структуру -python3 << EOF > /tmp/big_msg.json -import json -msg = "x" * 100000 -print(json.dumps({"MessageBody": msg})) -EOF -R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$BIG_QURL" --message-body "$(python3 -c 'print("x" * 100000)')" 2>&1) -[ -z "$R" ] || check "Граница: 100KB сообщение" "$R" "MessageId" - -# Отправка через меньший размер работает, проверим что получается -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$BIG_QURL" 2>&1) -SIZE=$(echo "$R" | jq -r '.Messages[0].Body' 2>/dev/null | wc -c) -TOTAL=$((TOTAL + 1)) -if [ "$SIZE" -gt 99000 ]; then - PASS=$((PASS + 1)); echo " ✅ Большое сообщение получено (≈${SIZE} bytes)" -else - FAIL=$((FAIL + 1)); echo " ❌ Размер не совпадает (ожидалось ≈100KB, получено $SIZE)" -fi - -# Проверим что очень большое требует ошибку (тестируем логику, не реальный размер) -# AWS CLI не позволит отправить > 256KB из-за своей валидации, поэтому ошибка будет на клиенте -R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$BIG_QURL" --message-body "test" 2>&1 && echo "OK") -check "Граница: обычное сообщение работает" "$R" "OK|MessageId" - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$BIG_QURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 17. Невалидные параметры (timeout, delay) ──" -# ═══════════════════════════════════════════ - -INV_Q="hc-inv-${TS}" -INV_QURL=$(qurl "$T1_ID" "$INV_Q") -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$INV_Q" >/dev/null 2>&1 || true - -# Receive с невалидными параметрами -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$INV_QURL" --visibility-timeout -1 2>&1) -check "Невалид: отрицательный VisibilityTimeout (ошибка)" "$R" "Invalid|invalid|Error|Parameter" - -# Visibility timeout слишком большой (43200 = 12 часов — AWS позволяет) -sqs "$T1_AK" "$T1_SK" send-message --queue-url "$INV_QURL" --message-body "msg" >/dev/null 2>&1 || true -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$INV_QURL" --visibility-timeout 43200 2>&1) -check "Невалид: VisibilityTimeout=43200 (должно быть OK или ошибка)" "$R" "Messages|Error|Invalid" - -# Receive с WaitTimeSeconds > 20 (AWS max 20) -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$INV_QURL" --wait-time-seconds 25 2>&1) -check "Невалид: WaitTimeSeconds=25 (max 20, ошибка)" "$R" "Invalid|Error|Parameter|out.of" - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$INV_QURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 18. Batch граничные случаи ──" -# ═══════════════════════════════════════════ - -BAT_Q="hc-bat-${TS}" -BAT_QURL=$(qurl "$T1_ID" "$BAT_Q") -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BAT_Q" >/dev/null 2>&1 || true - -# Batch с 1 сообщением (граница минимума) -BAT_1=$(python3 << 'EOF' -import json, sys -entries = [{"Id": "1", "MessageBody": "msg1"}] -print(json.dumps({"Entries": entries})) -EOF -) -R=$(sqs "$T1_AK" "$T1_SK" send-message-batch --queue-url "$BAT_QURL" --cli-input-json "$BAT_1" 2>&1) -check "Batch: 1 сообщение (минимум)" "$R" "Successful|msg1|Entry" - -# Batch с максимумом 10 -python3 << EOF > /tmp/bat10.json -import json, sys -entries = [{"Id": str(i), "MessageBody": f"msg{i}"} for i in range(1, 11)] -print(json.dumps({"Entries": entries})) -EOF -BAT_10=$(cat /tmp/bat10.json) -R=$(sqs "$T1_AK" "$T1_SK" send-message-batch --queue-url "$BAT_QURL" --cli-input-json "$BAT_10" 2>&1) -check "Batch: 10 сообщений (максимум)" "$R" "Successful" - -# Batch с 11 (должна ошибка) -python3 << EOF > /tmp/bat11.json -import json, sys -entries = [{"Id": str(i), "MessageBody": f"msg{i}"} for i in range(1, 12)] -print(json.dumps({"Entries": entries})) -EOF -BAT_11=$(cat /tmp/bat11.json) -R=$(sqs "$T1_AK" "$T1_SK" send-message-batch --queue-url "$BAT_QURL" --cli-input-json "$BAT_11" 2>&1) -check "Batch: 11 сообщений → ошибка (TooMany)" "$R" "TooMany|exceed|Maximum" - -# Batch с дублирующимися ID (должна ошибка) -python3 << EOF > /tmp/batdup.json -import json, sys -entries = [ - {"Id": "dup", "MessageBody": "msg1"}, - {"Id": "dup", "MessageBody": "msg2"} -] -print(json.dumps({"Entries": entries})) -EOF -BAT_DUP=$(cat /tmp/batdup.json) -R=$(sqs "$T1_AK" "$T1_SK" send-message-batch --queue-url "$BAT_QURL" --cli-input-json "$BAT_DUP" 2>&1) -check "Batch: дублирующиеся ID → ошибка" "$R" "Distinct|duplicate|Two.or.more" - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$BAT_Q" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 19. Receive с параметрами MaxMessages ──" -# ═══════════════════════════════════════════ - -REC_Q="hc-rec-${TS}" -REC_QURL=$(qurl "$T1_ID" "$REC_Q") -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$REC_Q" >/dev/null 2>&1 || true - -# Отправляем 5 сообщений -for i in {1..5}; do - sqs "$T1_AK" "$T1_SK" send-message --queue-url "$REC_QURL" --message-body "msg$i" >/dev/null 2>&1 || true -done - -# Receive с MaxNumberOfMessages=2 (должны быть ≤2 сообщения, зависит от реализации) -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$REC_QURL" --max-number-of-messages 2 2>&1) -COUNT=$(echo "$R" | jq '.Messages | length' 2>/dev/null || echo 0) -TOTAL=$((TOTAL + 1)) -if [ "$COUNT" -le 2 ]; then - PASS=$((PASS + 1)); echo " ✅ MaxMessages=2 → получено $COUNT (≤2)" -else - FAIL=$((FAIL + 1)); echo " ❌ MaxMessages (ожидалось ≤2, получено $COUNT)" -fi - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$REC_QURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 20. Двойные операции: double delete, non-existent ──" -# ═══════════════════════════════════════════ - -DBL_Q="hc-dbl-${TS}" -DBL_QURL=$(qurl "$T1_ID" "$DBL_Q") -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$DBL_Q" >/dev/null 2>&1 || true - -# Отправляем и получаем сообщение -sqs "$T1_AK" "$T1_SK" send-message --queue-url "$DBL_QURL" --message-body "test" >/dev/null 2>&1 || true -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$DBL_QURL" 2>&1) -RH=$(echo "$R" | jq -r '.Messages[0].ReceiptHandle' 2>/dev/null) - -# Удаляем сообщение дважды -sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$DBL_QURL" --receipt-handle "$RH" >/dev/null 2>&1 || true -R=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$DBL_QURL" --receipt-handle "$RH" 2>&1) -check "Double delete: второй delete → ошибка (MessageDoesNotExist)" "$R" "does.not|MessageDoesNotExist|not.found" - -# Delete с невалидным ReceiptHandle -R=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$DBL_QURL" --receipt-handle "invalid-rh-12345" 2>&1) -check "Delete: невалидный handle → ошибка" "$R" "does.not|MessageDoesNotExist|not.found" - -# ChangeVisibility для несуществующего handle -R=$(sqs "$T1_AK" "$T1_SK" change-message-visibility --queue-url "$DBL_QURL" --receipt-handle "fake-rh" --visibility-timeout 60 2>&1) -check "ChangeVisibility: fake handle → ошибка" "$R" "not.found|MessageNotInFlight|Invalid|in.flight" - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$DBL_QURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 21. Попытка создать очередь с дублирующимся именем ──" -# ═══════════════════════════════════════════ - -DUP_Q="hc-dupq-${TS}" -DUP_QURL=$(qurl "$T1_ID" "$DUP_Q") - -# Первая попытка — успех -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$DUP_Q" 2>&1) -check "Create: первая очередь создана" "$R" "Queue" - -# Вторая попытка с тем же именем -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$DUP_Q" 2>&1) -check "Create: дублирующееся имя (QueueExists или OK)" "$R" "QueueExists|Queue|already" - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$DUP_QURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 22. Операции на несуществующей очереди ──" -# ═══════════════════════════════════════════ - -FAKE_QURL="http://localhost:4100/queue/fake-q-never-existed-${TS}" - -# Send на несуществующую -R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$FAKE_QURL" --message-body "msg" 2>&1) -check "Send: на несуществующую → QueueNotFound" "$R" "QueueNotFound|does.not|specified" - -# Receive из несуществующей -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$FAKE_QURL" 2>&1) -check "Receive: из несуществующей → QueueNotFound" "$R" "QueueNotFound|does.not|specified" - -# SetAttributes на несуществующей -R=$(sqs "$T1_AK" "$T1_SK" set-queue-attributes --queue-url "$FAKE_QURL" --attributes VisibilityTimeout=30 2>&1) -check "SetAttributes: на несуществующей → ошибка" "$R" "QueueNotFound|not.found|error" - -echo "" - -# ═══════════════════════════════════════════ -echo "── 23. Специальные символы и кодировки ──" -# ═══════════════════════════════════════════ - -ENC_Q="hc-enc-${TS}" -ENC_QURL=$(qurl "$T1_ID" "$ENC_Q") -sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$ENC_Q" >/dev/null 2>&1 || true - -# Base64 закодированное -B64_MSG=$(echo -n "binary-data-test" | base64) -R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$ENC_QURL" --message-body "$B64_MSG" 2>&1) -check "Encoding: Base64 строка в теле" "$R" "MessageId" - -# Очень много спецсимволов -SPEC_MSG='!@#$%^&*()_+-=[]{}|;:,.<>?/~`"\'"'" -R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$ENC_QURL" --message-body "$SPEC_MSG" 2>&1) -check "Encoding: спецсимволы в теле" "$R" "MessageId" - -# Tabs, newlines, etc. -TAB_MSG="line1\tline2\nline3" -R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$ENC_QURL" --message-body "$TAB_MSG" 2>&1) -check "Encoding: whitespace (tab, newline)" "$R" "MessageId" - -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$ENC_QURL" >/dev/null 2>&1 || true - -echo "" - -# ═══════════════════════════════════════════ -echo "── 24. UI API — авторизация через JWT ──" -# ═══════════════════════════════════════════ - -# Авторизуемся через nubes JWT -UI_AUTH=$(curl -s --max-time 15 -X POST \ - -H "Content-Type: application/json" \ - -d "{\"token\":\"$NUBES_JWT\"}" \ - "${BASE_URL}/ui/api/auth" 2>&1) -check "UI API: POST /auth → tenant_id" "$UI_AUTH" "tenant_id" -UI_TID=$(echo "$UI_AUTH" | jq -r '.tenant_id' 2>/dev/null || true) -UI_AK=$(echo "$UI_AUTH" | jq -r '.access_key' 2>/dev/null || true) -UI_SK=$(echo "$UI_AUTH" | jq -r '.secret_key' 2>/dev/null || true) - -echo " UI Tenant ID: $UI_TID" - -echo "" - -# ═══════════════════════════════════════════ -echo "── 25б. UI API — создание очереди ──" -# ═══════════════════════════════════════════ - -# Создать очередь через UI API с JWT -R=$(ui_jwt POST "/tenants/${UI_TID}/queues" '{"name":"ui-queue-1"}') -check "UI API: создать очередь ui-queue-1 → имя" "$R" "ui-queue-1" - -# Дубль — та же очередь, не ошибка (идемпотентно) -R=$(ui_jwt POST "/tenants/${UI_TID}/queues" '{"name":"ui-queue-1"}') -check "UI API: повторное создание — idempotent (нет 500)" "$R" "ui-queue-1|error" - -# Список очередей через /ui/api содержит созданную -R=$(ui_jwt GET "/tenants/${UI_TID}/queues") -check "UI API: GET /queues содержит ui-queue-1" "$R" "ui-queue-1" - -echo "" - -# ═══════════════════════════════════════════ -echo "── 25. UI API — отправка и peek сообщений ──" -# ═══════════════════════════════════════════ - -# Отправить сообщение через UI -R=$(ui_jwt POST "/tenants/${UI_TID}/queues/ui-queue-1/messages" '{"body":"hello-from-ui"}') -check "UI API: send message → id" "$R" '"id"' -check "UI API: send message → status sent" "$R" "sent" - -# Отправить ещё одно -ui_jwt POST "/tenants/${UI_TID}/queues/ui-queue-1/messages" '{"body":"second-message"}' >/dev/null 2>&1 || true - -# Peek — должны видеть оба, без ReceiptHandle -R=$(ui_jwt GET "/tenants/${UI_TID}/queues/ui-queue-1/messages") -check "UI API: peek — hello-from-ui" "$R" "hello-from-ui" -check "UI API: peek — second-message" "$R" "second-message" -check_not "UI API: peek — НЕТ receipt_handle" "$R" "receipt_handle" - -# Peek не consumed — SQS receive-message тоже видит сообщения -QURL_UI=$(sqs "$UI_AK" "$UI_SK" list-queues 2>&1 \ - | python3 -c "import sys,json; d=json.load(sys.stdin); urls=d.get('QueueUrls',[]); print(next((u for u in urls if 'ui-queue-1' in u),''))" 2>/dev/null || true) -if [[ -n "$QURL_UI" ]]; then - RECV_AFTER_PEEK=$(sqs "$UI_AK" "$UI_SK" receive-message --queue-url "$QURL_UI" 2>&1) - check "UI API: peek не consumed — SQS receive-message видит сообщение" "$RECV_AFTER_PEEK" "hello-from-ui|Messages" -fi - -echo "" - -# ═══════════════════════════════════════════ -echo "── 26. UI API — purge очереди ──" -# ═══════════════════════════════════════════ - -R=$(ui_jwt DELETE "/tenants/${UI_TID}/queues/ui-queue-1/messages") -check "UI API: purge → 200/204 (нет ошибки)" "$R" "^\s*$|purged|ok|{}" - -# После purge peek должен вернуть пустой массив -R=$(ui_jwt GET "/tenants/${UI_TID}/queues/ui-queue-1/messages") -check_not "UI API: после purge peek пуст (нет hello-from-ui)" "$R" "hello-from-ui" - -# Отправим ещё одно после purge — должно работать -R=$(ui_jwt POST "/tenants/${UI_TID}/queues/ui-queue-1/messages" '{"body":"after-purge"}') -check "UI API: send после purge работает" "$R" '"id"' - -R=$(ui_jwt GET "/tenants/${UI_TID}/queues/ui-queue-1/messages") -check "UI API: peek после purge → after-purge" "$R" "after-purge" - -echo "" - -# ═══════════════════════════════════════════ -echo "── 27. UI API — удаление очереди ──" -# ═══════════════════════════════════════════ - -# Создать ещё одну очередь чтобы удалить -ui_jwt POST "/tenants/${UI_TID}/queues" '{"name":"ui-to-delete"}' >/dev/null 2>&1 || true - -R=$(ui_jwt GET "/tenants/${UI_TID}/queues") -check "UI API: ui-to-delete создана" "$R" "ui-to-delete" - -# Удалить -HTTP=$(curl -s --max-time 15 -o /dev/null -w "%{http_code}" -X DELETE \ - -H "Authorization: Bearer $NUBES_JWT" \ - "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-to-delete") -if [[ "$HTTP" == "204" || "$HTTP" == "200" ]]; then - PASS=$((PASS+1)); TOTAL=$((TOTAL+1)) - echo " ✅ UI API: DELETE /queues/ui-to-delete → HTTP $HTTP" -else - FAIL=$((FAIL+1)); TOTAL=$((TOTAL+1)) - echo " ❌ UI API: DELETE /queues/ui-to-delete → HTTP $HTTP" -fi - -R=$(ui_jwt GET "/tenants/${UI_TID}/queues") -check_not "UI API: ui-to-delete больше не в списке" "$R" "ui-to-delete" - -# Удаление несуществующей очереди → 404 -HTTP=$(curl -s --max-time 15 -o /dev/null -w "%{http_code}" -X DELETE \ - -H "Authorization: Bearer $NUBES_JWT" \ - "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/no-such-queue") -if [[ "$HTTP" == "404" ]]; then - PASS=$((PASS+1)); TOTAL=$((TOTAL+1)) - echo " ✅ UI API: DELETE несуществующей очереди → 404" -else - FAIL=$((FAIL+1)); TOTAL=$((TOTAL+1)) - echo " ❌ UI API: DELETE несуществующей очереди → HTTP $HTTP (ожидалось 404)" -fi - -echo "" - -# ═══════════════════════════════════════════ -echo "── 28. UI API — изоляция: чужой тенант недоступен ──" -# ═══════════════════════════════════════════ - -# Создать второго admin тенанта для теста изоляции -UI_T2=$(admin_api POST /admin/tenants '{"name":"ui-other-'"$TS"'","max_queues":5}') -UI_T2ID=$(echo "$UI_T2" | jq -r '.id') -UI_T2AK=$(echo "$UI_T2" | jq -r '.access_key') -UI_T2SK=$(echo "$UI_T2" | jq -r '.secret_key') - -# Создаём очередь во втором тенанте через admin API (не через UI JWT — JWT привязан к одному тенанту) -T2_Q_URL="${BASE_URL}/${UI_T2ID}/secret-queue" -sqs "$UI_T2AK" "$UI_T2SK" create-queue --queue-name "secret-queue" >/dev/null 2>&1 || true -sqs "$UI_T2AK" "$UI_T2SK" send-message --queue-url "$T2_Q_URL" --message-body "secret-data" >/dev/null 2>&1 || true - -# Текущий JWT тенант не видит очереди другого admin-created тенанта -R=$(ui_jwt GET "/tenants/${UI_TID}/queues") -check_not "UI API: JWT тенант не видит очереди T2" "$R" "secret-queue" - -# peek чужой очереди через JWT тенант → ошибка -R=$(ui_jwt GET "/tenants/${UI_TID}/queues/secret-queue/messages") -check "UI API: peek несуществующей очереди → ошибка" "$R" "not found|error|404" - -# Cleanup UI тенантов -admin_api DELETE "/admin/tenants/${UI_T2ID}" >/dev/null 2>&1 || true - -echo "" -# ═══════════════════════════════════════════ - -admin_api DELETE "/admin/tenants/${T1_ID}" >/dev/null 2>&1 || true -admin_api DELETE "/admin/tenants/${T2_ID}" >/dev/null 2>&1 || true -admin_api DELETE "/admin/tenants/${LIM_ID}" >/dev/null 2>&1 || true -admin_api DELETE "/admin/tenants/${UI_TID}" >/dev/null 2>&1 || true -# probe-test тенант (если оставался от предыдущих запусков) -PROBE_ID=$(admin_api GET /admin/tenants 2>/dev/null | jq -r '.[] | select(.name=="probe-test") | .id' 2>/dev/null || true) -[ -n "$PROBE_ID" ] && admin_api DELETE "/admin/tenants/${PROBE_ID}" >/dev/null 2>&1 || true - -echo " Тестовые тенанты удалены" -echo "" - -# ═══════════════════════════════════════════ -echo "╔══════════════════════════════════════════════════════════╗" -echo "║ ИТОГО ║" -echo "╠══════════════════════════════════════════════════════════╣" -printf "║ Всего: %-4s ✅ PASS: %-4s ❌ FAIL: %-4s ║\n" "$TOTAL" "$PASS" "$FAIL" -echo "╚══════════════════════════════════════════════════════════╝" - -[ "$FAIL" -eq 0 ] && exit 0 || exit 1 diff --git a/tests/payload_latency_probe.sh b/tests/payload_latency_probe.sh deleted file mode 100755 index 0a2638b..0000000 --- a/tests/payload_latency_probe.sh +++ /dev/null @@ -1,104 +0,0 @@ -#!/bin/bash -# Updated: 2026-04-12 05:40 UTC -# payload_latency_probe.sh — узкий замер SendMessage latency для payload 10KB и 32KB. - -set -euo pipefail - -# ysqs выполняет aws sqs для Yandex MQ. -ysqs() { - AWS_ACCESS_KEY_ID="$Y_AK" AWS_SECRET_ACCESS_KEY="$Y_SK" \ - AWS_DEFAULT_REGION="$Y_REGION" \ - aws --endpoint-url "$Y_EP" --output json sqs "$@" >/dev/null 2>&1 -} - -# osqs выполняет aws sqs для shared-sqs. -osqs() { - AWS_ACCESS_KEY_ID="$O_AK" AWS_SECRET_ACCESS_KEY="$O_SK" \ - AWS_DEFAULT_REGION="$O_REGION" \ - aws --endpoint-url "$O_EP" --output json sqs "$@" >/dev/null 2>&1 -} - -# ms_now возвращает текущее время в миллисекундах. -ms_now() { - date +%s%3N -} - -# calc_stats считает min/avg/max/p95 по файлу latency. -calc_stats() { - local file_path="$1" - sort -n "$file_path" | awk ' - { values[NR] = $1; total += $1 } - END { - p95_index = int(NR * 0.95) - if (p95_index < 1) p95_index = 1 - printf "%d %d %d %d\n", values[1], int(total / NR), values[NR], values[p95_index] - }' -} - -# cleanup удаляет временные объекты после замера. -cleanup() { - set +e - if [[ -n "${Y_QURL:-}" ]]; then - AWS_ACCESS_KEY_ID="$Y_AK" AWS_SECRET_ACCESS_KEY="$Y_SK" AWS_DEFAULT_REGION="$Y_REGION" \ - aws --endpoint-url "$Y_EP" --output json sqs delete-queue --queue-url "$Y_QURL" >/dev/null 2>&1 || true - fi - if [[ -n "${O_QURL:-}" ]]; then - AWS_ACCESS_KEY_ID="$O_AK" AWS_SECRET_ACCESS_KEY="$O_SK" AWS_DEFAULT_REGION="$O_REGION" \ - aws --endpoint-url "$O_EP" --output json sqs delete-queue --queue-url "$O_QURL" >/dev/null 2>&1 || true - fi - rm -rf "$TMPDIR" -} - -trap cleanup EXIT - -Y_AK="YCAJEQDz_Eg_i4C4M7TAen2fd" -Y_SK="YCMDfD8OKFK51knPyydwQOYts7Q81_3YBhv4sd_j" -Y_REGION="ru-central1" -Y_EP="https://message-queue.api.cloud.yandex.net" - -O_AK="SSAK-ed0b0c64dcc135adad9e11be" -O_SK="f917c133e4ad74cf37c8e3ba29d74a39f42f117a202bd672436c92fdb6b6f3dc" -O_REGION="us-east-1" -O_EP="https://qu.kube5s.ru" -O_TID="t-e0ce25e83be94c58" - -ITERATIONS=7 -TMPDIR=$(mktemp -d /tmp/payload_latency_probe_XXXXX) -TS=$(date +%s) - -head -c 10240 /dev/urandom | base64 | head -c 10240 > "$TMPDIR/payload_10k.txt" -head -c 32768 /dev/urandom | base64 | head -c 32768 > "$TMPDIR/payload_32k.txt" - -Y_QNAME="payload-probe-y-${TS}" -O_QNAME="payload-probe-o-${TS}" - -Y_CREATE_RAW=$(AWS_ACCESS_KEY_ID="$Y_AK" AWS_SECRET_ACCESS_KEY="$Y_SK" AWS_DEFAULT_REGION="$Y_REGION" aws --endpoint-url "$Y_EP" --output json sqs create-queue --queue-name "$Y_QNAME") -Y_QURL=$(echo "$Y_CREATE_RAW" | python3 -c 'import json,sys; print(json.load(sys.stdin)["QueueUrl"])') -osqs create-queue --queue-name "$O_QNAME" -O_QURL="${O_EP}/${O_TID}/${O_QNAME}" - -run_probe() { - local provider_name="$1" - local queue_url="$2" - local payload_file="$3" - local result_file="$4" - local runner="$5" - - : > "$result_file" - for _ in $(seq 1 "$ITERATIONS"); do - local started_at - started_at=$(ms_now) - "$runner" send-message --queue-url "$queue_url" --message-body "file://$payload_file" - echo $(( $(ms_now) - started_at )) >> "$result_file" - done - - local stats_line - stats_line=$(calc_stats "$result_file") - echo "${provider_name}|$(echo "$stats_line" | awk '{print $1"|"$2"|"$3"|"$4}')" -} - -echo "payload_kb|provider|min|avg|max|p95" -run_probe "yandex" "$Y_QURL" "$TMPDIR/payload_10k.txt" "$TMPDIR/y_10k.txt" ysqs | awk -F'|' '{print "10|"$0}' -run_probe "shared-sqs" "$O_QURL" "$TMPDIR/payload_10k.txt" "$TMPDIR/o_10k.txt" osqs | awk -F'|' '{print "10|"$0}' -run_probe "yandex" "$Y_QURL" "$TMPDIR/payload_32k.txt" "$TMPDIR/y_32k.txt" ysqs | awk -F'|' '{print "32|"$0}' -run_probe "shared-sqs" "$O_QURL" "$TMPDIR/payload_32k.txt" "$TMPDIR/o_32k.txt" osqs | awk -F'|' '{print "32|"$0}' \ No newline at end of file diff --git a/tests/quick_test.sh b/tests/quick_test.sh deleted file mode 100755 index c52b91d..0000000 --- a/tests/quick_test.sh +++ /dev/null @@ -1,267 +0,0 @@ -#!/bin/bash -# tests/quick_test.sh — Быстрая проверка shared-sqs (smoke test) -# Created: 2026-04-09 -# Updated: 2026-04-11 — полная поддержка JWT (nubes api_token), все 17 SQS команд, -# TagQueue/UntagQueue/ListQueueTags/ChangeMessageVisibilityBatch, -# UI API с авторизацией через Bearer JWT. -# Покрывает: health, admin CRUD, все SQS команды, UI API с JWT. -# Требования: curl, aws CLI, python3 -# Запуск: -# bash tests/quick_test.sh -# BASE_URL=https://qu.kube5s.ru ADMIN_TOKEN=... NUBES_JWT=... bash tests/quick_test.sh - -set -uo pipefail - -BASE_URL="${BASE_URL:-https://qu.kube5s.ru}" -ADMIN_TOKEN="${ADMIN_TOKEN:-sqs-admin-7a7d8bd0c060a75c198d48680f34077a}" -NUBES_JWT="${NUBES_JWT:-eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJhdXRoLWFwaSIsInN1YiI6IjAxOTllMzI1LTFjZGYtN2NkYS05MzE5LWU1MzAyYTg1ZTI5MSIsImV4cCI6MTc4NjkzMjI2MCwiaWF0IjoxNzcxMzgwMjYwLCJqdGkiOiIzOTQ3ZTgyMy0yNjljLTQ0MTAtYmU0My1iNGVkNTc1Njg0ZTQiLCJhdXRoX3RpbWUiOjAsInR5cCI6IiIsImF6cCI6IiIsInNlc3Npb25fc3RhdGUiOiIiLCJhY3IiOiIiLCJhbGxvd2VkLW9yaWdpbnMiOm51bGwsInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6bnVsbH0sInJlc291cmNlX2FjY2VzcyI6eyJhY2NvdW50Ijp7InJvbGVzIjpudWxsfX0sInNjb3BlIjoiIiwic2lkIjoiIiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJuYW1lIjoiIiwiQ2xpZW50SUQiOiIiLCJncm91cHMiOm51bGwsInByZWZlcnJlZF91c2VybmFtZSI6IiIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiIsImVtYWlsIjoidGF6ZXRAbmFyb2QucnUifQ.hzpIIqNWkKIoYUXDaLY7DLyGKH70rz0ZTqanv19qxF10i3N1t1g_KknA4Qsw1MduTyLzIz7y5SRSr4PSQ1gzR0vB_C0GudSFUhyBNNKkS4ClhRDWW9eN_IIEljbiJMLQi2L07XJ7Y5DQ0sIHRAPLkreCDFMKQ0yTCrKoScCJIDuUqzaTcOaX-hfjaxW8iV0SZMDxl0C5O3tke0btxkaLBaAcWH0V-1yu2r2m29fyU33FqikF0xAcDXiuZphfsrShKQYArZjKAphYCP_Vpmr-1sdjinkn8sPSk1qZny0rka8G6WVZUGaZSOnW8SYNLVUwdqtuQmK-Y18o7U0Suzrsjg}" -REGION="us-east-1" -TS=$(date +%s) - -PASS=0 -FAIL=0 - -ok() { echo " ✅ $1"; PASS=$((PASS+1)); } -fail() { echo " ❌ $1"; FAIL=$((FAIL+1)); } - -check() { - local label="$1" body="$2" pattern="$3" - if echo "$body" | grep -qE "$pattern"; then ok "$label"; else fail "$label: $(echo "$body" | head -3)"; fi -} - -check_not() { - local label="$1" body="$2" pattern="$3" - if echo "$body" | grep -qE "$pattern"; then fail "$label"; else ok "$label"; fi -} - -check_http() { - local label="$1" want="$2" got="$3" - if [[ "$got" == "$want" ]]; then ok "$label (HTTP $got)"; else fail "$label — ожидалось $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="$REGION" \ - aws --endpoint-url "$BASE_URL" --output json sqs "$@" 2>&1 -} - -# admin — Admin API запрос с Bearer admin token -admin() { - local method="$1" path="$2" body="${3:-}" - if [[ -n "$body" ]]; then - curl -sf --max-time 15 -X "$method" \ - -H "Authorization: Bearer $ADMIN_TOKEN" \ - -H "Content-Type: application/json" \ - -d "$body" "${BASE_URL}${path}" 2>&1 - else - curl -sf --max-time 15 -X "$method" \ - -H "Authorization: Bearer $ADMIN_TOKEN" \ - "${BASE_URL}${path}" 2>&1 - fi -} - -# ui — UI API запрос с Bearer JWT (nubes токен) -ui() { - local method="$1" path="$2" body="${3:-}" - if [[ -n "$body" ]]; then - curl -sf --max-time 15 -X "$method" \ - -H "Authorization: Bearer $NUBES_JWT" \ - -H "Content-Type: application/json" \ - -d "$body" "${BASE_URL}/ui/api${path}" 2>&1 - else - curl -sf --max-time 15 -X "$method" \ - -H "Authorization: Bearer $NUBES_JWT" \ - "${BASE_URL}/ui/api${path}" 2>&1 - fi -} - -echo "════════════════════════════════════════" -echo " shared-sqs Quick Test" -echo " Endpoint: $BASE_URL" -echo "════════════════════════════════════════" -echo "" - -# ── 1. Health ── -echo "── 1. Health ──" -R=$(curl -sf --max-time 10 "${BASE_URL}/health" 2>&1) -check "GET /health → ok" "$R" "[Oo][Kk]|status" -echo "" - -# ── 2. UI API авторизация (JWT auth) ── -echo "── 2. UI API авторизация ──" -R=$(curl -sf --max-time 15 -X POST \ - -H "Content-Type: application/json" \ - -d "{\"token\":\"$NUBES_JWT\"}" \ - "${BASE_URL}/ui/api/auth" 2>&1) -check "POST /ui/api/auth → tenant_id" "$R" "tenant_id" -UI_TID=$(echo "$R" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tenant_id',''))" 2>/dev/null || true) -echo " UI Tenant ID: $UI_TID" -echo "" - -# ── 3. Создание тенанта через Admin API ── -echo "── 3. Admin API — создание тенанта ──" -RESP=$(admin POST /admin/tenants '{"name":"quick-'"$TS"'","max_queues":10}') -check "POST /admin/tenants → access_key" "$RESP" "access_key" -AK=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_key'])") -SK=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['secret_key'])") -TID=$(echo "$RESP" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") -echo " Tenant ID: $TID" -echo " Access Key: $AK" -echo "" - -QNAME="quick-q-$TS" -QURL="${BASE_URL}/${TID}/${QNAME}" - -# ── 4. SQS Queue CRUD ── -echo "── 4. SQS Queue CRUD ──" -R=$(sqs "$AK" "$SK" create-queue --queue-name "$QNAME") -check "CreateQueue → QueueUrl" "$R" "QueueUrl" - -R=$(sqs "$AK" "$SK" list-queues) -check "ListQueues → очередь в списке" "$R" "$QNAME" - -R=$(sqs "$AK" "$SK" get-queue-url --queue-name "$QNAME") -check "GetQueueUrl → QueueUrl" "$R" "QueueUrl" - -R=$(sqs "$AK" "$SK" get-queue-attributes --queue-url "$QURL" --attribute-names All) -check "GetQueueAttributes → VisibilityTimeout" "$R" "VisibilityTimeout" - -R=$(sqs "$AK" "$SK" set-queue-attributes --queue-url "$QURL" --attributes '{"VisibilityTimeout":"60"}' 2>&1 && echo "OK" || echo "FAIL") -check "SetQueueAttributes → OK" "$R" "OK" -echo "" - -# ── 5. TagQueue / UntagQueue / ListQueueTags ── -echo "── 5. Tags API ──" -R=$(sqs "$AK" "$SK" tag-queue --queue-url "$QURL" --tags env=test,owner=qa 2>&1 && echo "OK" || echo "FAIL") -check "TagQueue → OK" "$R" "OK" - -R=$(sqs "$AK" "$SK" list-queue-tags --queue-url "$QURL") -check "ListQueueTags → env=test" "$R" "env|test" - -R=$(sqs "$AK" "$SK" untag-queue --queue-url "$QURL" --tag-keys env 2>&1 && echo "OK" || echo "FAIL") -check "UntagQueue → OK" "$R" "OK" - -R=$(sqs "$AK" "$SK" list-queue-tags --queue-url "$QURL") -check_not "ListQueueTags → env удалён" "$R" '"env"' -echo "" - -# ── 6. Сообщения: Send / Receive / Delete ── -echo "── 6. Send / Receive / Delete ──" -R=$(sqs "$AK" "$SK" send-message --queue-url "$QURL" --message-body "hello-$TS") -check "SendMessage → MessageId" "$R" "MessageId" -check "SendMessage → MD5" "$R" "MD5OfMessageBody" - -R=$(sqs "$AK" "$SK" receive-message --queue-url "$QURL" --visibility-timeout 30) -check "ReceiveMessage → тело" "$R" "hello-$TS" -RECEIPT=$(echo "$R" | python3 -c "import sys,json; msgs=json.load(sys.stdin).get('Messages',[]); print(msgs[0]['ReceiptHandle'] if msgs else '')" 2>/dev/null || true) - -if [[ -n "$RECEIPT" ]]; then - R=$(sqs "$AK" "$SK" change-message-visibility --queue-url "$QURL" --receipt-handle "$RECEIPT" --visibility-timeout 60 2>&1 && echo "OK" || echo "FAIL") - check "ChangeMessageVisibility → OK" "$R" "OK" - - sqs "$AK" "$SK" delete-message --queue-url "$QURL" --receipt-handle "$RECEIPT" >/dev/null 2>&1 - ok "DeleteMessage → без ошибок" -else - fail "ReceiptHandle не получен" -fi -echo "" - -# ── 7. Batch операции ── -echo "── 7. Batch ──" -R=$(sqs "$AK" "$SK" send-message-batch --queue-url "$QURL" \ - --entries '[{"Id":"b1","MessageBody":"batch-1"},{"Id":"b2","MessageBody":"batch-2"},{"Id":"b3","MessageBody":"batch-3"}]') -check "SendMessageBatch → Successful" "$R" "Successful" - -R=$(sqs "$AK" "$SK" receive-message --queue-url "$QURL" --max-number-of-messages 3) -RECEIPTS=$(echo "$R" | python3 -c "import sys,json; msgs=json.load(sys.stdin).get('Messages',[]); print('\n'.join(m['ReceiptHandle'] for m in msgs))" 2>/dev/null || true) - -# ChangeMessageVisibilityBatch -if [[ -n "$RECEIPTS" ]]; then - CMV_ENTRIES=$(echo "$RECEIPTS" | python3 -c " -import sys -entries = [] -for i, rh in enumerate(sys.stdin.read().strip().split('\n'), 1): - if rh: - entries.append('{\"Id\":\"cmv%d\",\"ReceiptHandle\":\"%s\",\"VisibilityTimeout\":60}' % (i, rh)) -print('[' + ','.join(entries) + ']') -" 2>/dev/null || echo "[]") - if [[ "$CMV_ENTRIES" != "[]" ]]; then - R=$(sqs "$AK" "$SK" change-message-visibility-batch --queue-url "$QURL" --entries "$CMV_ENTRIES") - check "ChangeMessageVisibilityBatch → Successful" "$R" "Successful" - fi - - DEL_ENTRIES=$(echo "$RECEIPTS" | python3 -c " -import sys -entries = [] -for i, rh in enumerate(sys.stdin.read().strip().split('\n'), 1): - if rh: - entries.append('{\"Id\":\"d%d\",\"ReceiptHandle\":\"%s\"}' % (i, rh)) -print('[' + ','.join(entries) + ']') -" 2>/dev/null || echo "[]") - if [[ "$DEL_ENTRIES" != "[]" ]]; then - R=$(sqs "$AK" "$SK" delete-message-batch --queue-url "$QURL" --entries "$DEL_ENTRIES") - check "DeleteMessageBatch → Successful" "$R" "Successful" - fi -fi -echo "" - -# ── 8. PurgeQueue ── -echo "── 8. PurgeQueue ──" -sqs "$AK" "$SK" send-message --queue-url "$QURL" --message-body "purge-me" >/dev/null 2>&1 || true -R=$(sqs "$AK" "$SK" purge-queue --queue-url "$QURL" 2>&1 && echo "OK" || echo "FAIL") -check "PurgeQueue → OK" "$R" "OK" -echo "" - -# ── 9. UI API с JWT ── -echo "── 9. UI API с JWT ──" -R=$(ui GET /health) -check "GET /ui/api/health → ok" "$R" "ok|status" - -if [[ -n "$UI_TID" ]]; then - R=$(ui GET "/tenants/${UI_TID}/queues") - check "UI GET /tenants/{id}/queues → ответ" "$R" "\[|\]|name|queues" - - R=$(ui POST "/tenants/${UI_TID}/queues" '{"name":"ui-q-'"$TS"'"}') - check "UI POST /queues → создана" "$R" "ui-q-$TS|name" - - R=$(ui POST "/tenants/${UI_TID}/queues/ui-q-${TS}/messages" '{"body":"ui-msg-'"$TS"'"}') - check "UI POST /messages → id" "$R" '"id"' - - R=$(ui GET "/tenants/${UI_TID}/queues/ui-q-${TS}/messages") - check "UI GET /messages → тело" "$R" "ui-msg-$TS" - check_not "UI GET /messages → нет receipt_handle" "$R" "receipt_handle" - - R=$(ui DELETE "/tenants/${UI_TID}/queues/ui-q-${TS}/messages" "") - check_not "UI DELETE /messages (purge) → сообщение исчезло" \ - "$(ui GET "/tenants/${UI_TID}/queues/ui-q-${TS}/messages")" "ui-msg-$TS" - - HTTP=$(curl -sf --max-time 15 -o /dev/null -w "%{http_code}" -X DELETE \ - -H "Authorization: Bearer $NUBES_JWT" \ - "${BASE_URL}/ui/api/tenants/${UI_TID}/queues/ui-q-${TS}") - if [[ "$HTTP" == "204" || "$HTTP" == "200" ]]; then - ok "UI DELETE /queues/ui-q → HTTP $HTTP" - else - fail "UI DELETE /queues/ui-q → HTTP $HTTP (ожидалось 200/204)" - fi -else - fail "UI API: JWT auth не дал tenant_id — пропускаем UI тесты" -fi -echo "" - -# ── 10. DeleteQueue ── -echo "── 10. DeleteQueue ──" -R=$(sqs "$AK" "$SK" delete-queue --queue-url "$QURL" 2>&1 && echo "OK" || echo "FAIL") -check "DeleteQueue → OK" "$R" "OK" -echo "" - -# ── Cleanup ── -echo "── Cleanup ──" -admin DELETE "/admin/tenants/$TID" >/dev/null 2>&1 && ok "DELETE тенанта" || fail "DELETE тенанта" -echo "" - -echo "════════════════════════════════════════" -printf " Результат: ✅ %d ❌ %d\n" "$PASS" "$FAIL" -echo "════════════════════════════════════════" - -[[ $FAIL -eq 0 ]] && exit 0 || exit 1 diff --git a/tests/shared_sqs_test.sh b/tests/shared_sqs_test.sh deleted file mode 100755 index 153a7bc..0000000 --- a/tests/shared_sqs_test.sh +++ /dev/null @@ -1,211 +0,0 @@ -#!/usr/bin/env bash -# tests/shared_sqs_test.sh -# Интеграционные тесты shared-sqs: Admin API, изоляция тенантов, CRUD, лимиты -# Created: 2026-04-09 - -set -euo pipefail - -BASE_URL="${BASE_URL:-http://localhost:4100}" -ADMIN_TOKEN="${ADMIN_TOKEN:-dev-token-123}" -PASS=0 -FAIL=0 - -pass() { echo " [PASS] $1"; PASS=$((PASS+1)); } -fail() { echo " [FAIL] $1"; FAIL=$((FAIL+1)); } - -check_status() { - local label="$1" expected="$2" actual="$3" - if [[ "$actual" == "$expected" ]]; then - pass "$label (HTTP $actual)" - else - fail "$label -- expected HTTP $expected, got $actual" - fi -} - -check_contains() { - local label="$1" pattern="$2" body="$3" - if echo "$body" | grep -q "$pattern"; then - pass "$label" - else - fail "$label -- pattern '$pattern' not found in: $body" - fi -} - -check_not_contains() { - local label="$1" pattern="$2" body="$3" - if ! echo "$body" | grep -q "$pattern"; then - pass "$label" - else - fail "$label -- pattern '$pattern' FOUND but should be absent" - fi -} - -sqs_req() { - local access_key="$1" secret_key="$2" action="$3" - shift 3 - AWS_ACCESS_KEY_ID="$access_key" \ - AWS_SECRET_ACCESS_KEY="$secret_key" \ - AWS_DEFAULT_REGION="us-east-1" \ - aws --endpoint-url "$BASE_URL" --output json sqs "$action" "$@" 2>&1 -} - -sqs_req_status() { - local access_key="$1" secret_key="$2" action="$3" - shift 3 - AWS_ACCESS_KEY_ID="$access_key" \ - AWS_SECRET_ACCESS_KEY="$secret_key" \ - AWS_DEFAULT_REGION="us-east-1" \ - aws --endpoint-url "$BASE_URL" --output json sqs "$action" "$@" > /dev/null 2>&1 - echo $? -} - -echo "========================================" -echo " shared-sqs integration tests" -echo " BASE_URL=$BASE_URL" -echo "========================================" - -echo "" -echo "--- 1. Health check ---" -STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/health") -check_status "GET /health" "200" "$STATUS" - -echo "" -echo "--- 2. Admin API -- без токена ---" -STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X GET "$BASE_URL/admin/tenants") -check_status "GET /admin/tenants без токена -> 401" "401" "$STATUS" - -STATUS=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/admin/tenants" \ - -H "Content-Type: application/json" -d '{"name":"test"}') -check_status "POST /admin/tenants без токена -> 401" "401" "$STATUS" - -echo "" -echo "--- 3. Admin API -- создание тенантов ---" -RESP_A=$(curl -s -X POST "$BASE_URL/admin/tenants" \ - -H "Authorization: Bearer $ADMIN_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name":"tenant-a","max_queues":10}') -check_contains "Создать tenant-a -- access_key" "access_key" "$RESP_A" -check_contains "Создать tenant-a -- secret_key" "secret_key" "$RESP_A" -check_contains "Создать tenant-a -- id" "\"id\"" "$RESP_A" - -ACCESS_A=$(echo "$RESP_A" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['access_key'])") -SECRET_A=$(echo "$RESP_A" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['secret_key'])") -ID_A=$(echo "$RESP_A" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['id'])") - -RESP_B=$(curl -s -X POST "$BASE_URL/admin/tenants" \ - -H "Authorization: Bearer $ADMIN_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name":"tenant-b","max_queues":10}') -check_contains "Создать tenant-b -- access_key" "access_key" "$RESP_B" - -ACCESS_B=$(echo "$RESP_B" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['access_key'])") -SECRET_B=$(echo "$RESP_B" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['secret_key'])") -ID_B=$(echo "$RESP_B" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['id'])") - -echo "" -echo "--- 4. Admin API -- list/get ---" -LIST=$(curl -s "$BASE_URL/admin/tenants" -H "Authorization: Bearer $ADMIN_TOKEN") -check_contains "GET /admin/tenants -- tenant-a" "tenant-a" "$LIST" -check_contains "GET /admin/tenants -- tenant-b" "tenant-b" "$LIST" -check_not_contains "GET /admin/tenants -- НЕТ secret_key" "secret_key" "$LIST" - -GET_A=$(curl -s "$BASE_URL/admin/tenants/$ID_A" -H "Authorization: Bearer $ADMIN_TOKEN") -check_contains "GET /admin/tenants/{id} -- tenant-a" "tenant-a" "$GET_A" -check_not_contains "GET /admin/tenants/{id} -- нет secret_key" "secret_key" "$GET_A" - -STATUS404=$(curl -s -o /dev/null -w "%{http_code}" "$BASE_URL/admin/tenants/no-such-id" \ - -H "Authorization: Bearer $ADMIN_TOKEN") -check_status "GET несуществующего тенанта -> 404" "404" "$STATUS404" - -echo "" -echo "--- 5. CRUD flow (tenant-a) ---" -QUEUE_URL_A=$(sqs_req "$ACCESS_A" "$SECRET_A" create-queue --queue-name my-queue \ - | python3 -c "import sys,json; print(json.load(sys.stdin)['QueueUrl'])") -check_contains "CreateQueue -> QueueUrl содержит my-queue" "my-queue" "$QUEUE_URL_A" -check_contains "CreateQueue -> QueueUrl содержит ID тенанта" "$ID_A" "$QUEUE_URL_A" - -MSG_ID=$(sqs_req "$ACCESS_A" "$SECRET_A" send-message \ - --queue-url "$QUEUE_URL_A" --message-body "hello-world" \ - | python3 -c "import sys,json; print(json.load(sys.stdin)['MessageId'])") -check_contains "SendMessage -> MessageId" "-" "$MSG_ID" - -RECV=$(sqs_req "$ACCESS_A" "$SECRET_A" receive-message --queue-url "$QUEUE_URL_A") -check_contains "ReceiveMessage -> тело hello-world" "hello-world" "$RECV" - -RECEIPT=$(echo "$RECV" | python3 -c "import sys,json; msgs=json.load(sys.stdin).get('Messages',[]); print(msgs[0]['ReceiptHandle'] if msgs else '')") -if [[ -n "$RECEIPT" ]]; then - sqs_req "$ACCESS_A" "$SECRET_A" delete-message \ - --queue-url "$QUEUE_URL_A" --receipt-handle "$RECEIPT" > /dev/null 2>&1 - pass "DeleteMessage -- без ошибок" -else - fail "DeleteMessage -- нет ReceiptHandle" -fi - -echo "" -echo "--- 6. Изоляция тенантов ---" -QUEUE_URL_B=$(sqs_req "$ACCESS_B" "$SECRET_B" create-queue --queue-name my-queue \ - | python3 -c "import sys,json; print(json.load(sys.stdin)['QueueUrl'])") -check_contains "tenant-B CreateQueue my-queue -> ID tenant-B в URL" "$ID_B" "$QUEUE_URL_B" - -sqs_req "$ACCESS_A" "$SECRET_A" send-message \ - --queue-url "$QUEUE_URL_A" --message-body "msg-for-A" > /dev/null 2>&1 - -RECV_B=$(sqs_req "$ACCESS_B" "$SECRET_B" receive-message --queue-url "$QUEUE_URL_B" 2>&1 || true) -check_not_contains "Изоляция: tenant-B НЕ получает msg-for-A" "msg-for-A" "$RECV_B" - -LIST_B=$(sqs_req "$ACCESS_B" "$SECRET_B" list-queues 2>&1 || true) -check_not_contains "ListQueues tenant-B -- нет ID tenant-A" "$ID_A" "$LIST_B" - -LIST_A=$(sqs_req "$ACCESS_A" "$SECRET_A" list-queues 2>&1 || true) -check_not_contains "ListQueues tenant-A -- нет ID tenant-B" "$ID_B" "$LIST_A" - -echo "" -echo "--- 7. SQS с невалидным ключом -> ошибка ---" -EXIT_FAKE=$(sqs_req_status "FAKE-KEY-0000000000" "fakesecret0000" list-queues) -if [[ "$EXIT_FAKE" != "0" ]]; then - pass "Невалидный access_key -> ошибка auth" -else - fail "Невалидный access_key -> ожидалась ошибка, получен 200" -fi - -echo "" -echo "--- 8. Лимит очередей (max_queues=2) ---" -RESP_LIM=$(curl -s -X POST "$BASE_URL/admin/tenants" \ - -H "Authorization: Bearer $ADMIN_TOKEN" \ - -H "Content-Type: application/json" \ - -d '{"name":"limited","max_queues":2}') -ACCESS_LIM=$(echo "$RESP_LIM" | python3 -c "import sys,json; print(json.load(sys.stdin)['access_key'])") -SECRET_LIM=$(echo "$RESP_LIM" | python3 -c "import sys,json; print(json.load(sys.stdin)['secret_key'])") - -sqs_req "$ACCESS_LIM" "$SECRET_LIM" create-queue --queue-name q1 > /dev/null 2>&1 -pass "Очередь 1 из 2 -- создана" -sqs_req "$ACCESS_LIM" "$SECRET_LIM" create-queue --queue-name q2 > /dev/null 2>&1 -pass "Очередь 2 из 2 -- создана" - -EXIT3=$(sqs_req_status "$ACCESS_LIM" "$SECRET_LIM" create-queue --queue-name q3) -if [[ "$EXIT3" != "0" ]]; then - pass "Очередь 3 при max_queues=2 -> лимит (ошибка)" -else - fail "Очередь 3 при max_queues=2 -> ожидалась ошибка, создалась!" -fi - -echo "" -echo "--- 9. DELETE tenant -> очереди удалены ---" -STATUS_DEL=$(curl -s -o /dev/null -w "%{http_code}" -X DELETE \ - "$BASE_URL/admin/tenants/$ID_B" \ - -H "Authorization: Bearer $ADMIN_TOKEN") -check_status "DELETE tenant-b -> 204" "204" "$STATUS_DEL" - -EXIT_AFTER=$(sqs_req_status "$ACCESS_B" "$SECRET_B" list-queues) -if [[ "$EXIT_AFTER" != "0" ]]; then - pass "После DELETE tenant -- его ключи -> ошибка auth" -else - fail "После DELETE tenant -- его ключи вернули 200" -fi - -echo "" -echo "========================================" -echo " Результат: PASS=$PASS FAIL=$FAIL" -echo "========================================" - -[[ $FAIL -eq 0 ]] && exit 0 || exit 1 diff --git a/tests/stress_test.sh b/tests/stress_test.sh deleted file mode 100755 index b248102..0000000 --- a/tests/stress_test.sh +++ /dev/null @@ -1,966 +0,0 @@ -#!/bin/bash -# stress_test.sh — ЖЁСТКИЙ стресс-тест shared-sqs -# Created: 2026-04-11 -# Updated: 2026-04-11 — v2: масштаб, long-poll, race, batch, 50 очередей, memory, multi-kill -# Покрывает: -# 1. Подготовка (тенанты, очереди) -# 2. Параллельная отправка — 50 воркеров × 20 msg = 1000 сообщений -# 3. Параллельное чтение + удаление (гонка) -# 4. Multi-tenant изоляция (5 тенантов одновременно) -# 5. Burst — 100 сообщений одновременно -# 6. Long-polling + параллельные producer'ы -# 7. Двойное удаление (race на receipt handle) -# 8. Batch-операции конкурентно (SendMessageBatch + DeleteMessageBatch) -# 9. Много очередей — 30 очередей на тенанта -# 10. Kill pod + восстановление из Redis -# 11. Redis disconnect / reconnect -# 12. Смешанная нагрузка (send+recv+delete+getattr, 20s) -# 13. Memory leak check (RSS до/после нагрузки) -# 14. Multi-kill: kill → восстановление → kill → восстановление -# 15. Cleanup -# Запуск: 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" -POD_LABEL="app.kubernetes.io/name=shared-sqs" - -# Параметры нагрузки — ЖЁСТКИЕ -CONCURRENT_WORKERS=20 # параллельных воркеров -MESSAGES_PER_WORKER=10 # сообщений на воркер (итого 1000) -BURST_SIZE=80 # размер burst-пакета -TENANT_COUNT=5 # тенантов для multi-tenant теста -QUEUE_FLOOD_COUNT=25 # очередей на тенанта -MIXED_DURATION=15 # длительность смешанной нагрузки (сек) -BATCH_WORKERS=8 # воркеров для batch-операций - -PASS=0 -FAIL=0 -TOTAL=0 -TMPDIR_STRESS=$(mktemp -d /tmp/sqs_stress_XXXXX) - -# ═══════════════════════════════════════════ -# ХЕЛПЕРЫ -# ═══════════════════════════════════════════ - -json_field() { - python3 -c "import sys,json; print(json.load(sys.stdin).get('$1',''))" 2>/dev/null -} - -# progress_dot — печатает точку без переноса строки (прогресс) -progress_dot() { - printf "." >&2 -} - -# progress_done — завершает строку прогресса -progress_done() { - echo "" >&2 -} - -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 -} - -check_le() { - local name="$1" got="$2" want="$3" - TOTAL=$((TOTAL + 1)) - if [[ "$got" -le "$want" ]]; then - PASS=$((PASS + 1)) - echo " ✅ $name ($got <= $want)" - else - FAIL=$((FAIL + 1)) - echo " ❌ $name: ожидалось <=$want, получено=$got" - fi -} - -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() { - 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 -} - -qurl() { echo "${BASE_URL}/${1}/${2}"; } - -wait_for_pod_ready() { - local max_wait=120 waited=0 - echo " ⏳ Ожидание пода (max ${max_wait}s)..." - while [[ $waited -lt $max_wait ]]; do - local ready - ready=$(kubectl get pods -n "$NAMESPACE" -l "$POD_LABEL" \ - -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)) - printf "." >&2 - done - echo "" - echo " ⚠️ Под не готов после ${max_wait}s" - return 1 -} - -wait_for_healthy() { - local max_wait=120 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)) - printf "." >&2 - done - echo "" - echo " ⚠️ Сервис не отвечает после ${max_wait}s" - return 1 -} - -# get_pod_rss_mb — RSS памяти текущего пода в MB -get_pod_rss_mb() { - local pod - pod=$(kubectl get pods -n "$NAMESPACE" -l "$POD_LABEL" \ - -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) - # kubectl top может не работать — fallback на /proc - local rss_kb - rss_kb=$(kubectl exec -n "$NAMESPACE" "$pod" -- cat /proc/1/status 2>/dev/null \ - | grep VmRSS | awk '{print $2}') - if [[ -n "$rss_kb" ]]; then - echo $((rss_kb / 1024)) - else - echo "0" - fi -} - -kill_pod() { - local pod - pod=$(kubectl get pods -n "$NAMESPACE" -l "$POD_LABEL" \ - -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) - echo " 🔪 Kill: $pod" - kubectl delete pod "$pod" -n "$NAMESPACE" --grace-period=0 --force 2>&1 | head -1 -} - -cleanup() { rm -rf "$TMPDIR_STRESS" 2>/dev/null; } -trap cleanup EXIT - -STARTED_AT=$(date +%s) - -echo "╔═══════════════════════════════════════════════════════════════════╗" -echo "║ shared-sqs STRESS TEST v2 — $(date '+%Y-%m-%d %H:%M:%S') ║" -echo "║ Endpoint: ${BASE_URL} ║" -echo "║ Workers: ${CONCURRENT_WORKERS} Msgs/worker: ${MESSAGES_PER_WORKER} Burst: ${BURST_SIZE} Tenants: ${TENANT_COUNT} ║" -echo "║ Queue-flood: ${QUEUE_FLOOD_COUNT} Batch-workers: ${BATCH_WORKERS} Mixed: ${MIXED_DURATION}s ║" -echo "╚═══════════════════════════════════════════════════════════════════╝" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 1. ПОДГОТОВКА — тенанты и очереди -# ═══════════════════════════════════════════════════════════ -echo "═══ 1. Подготовка — создаём $TENANT_COUNT тенантов ═══" - -declare -a TENANT_AK TENANT_SK TENANT_ID -for i in $(seq 1 $TENANT_COUNT); do - R=$(admin_api POST "/admin/tenants" "{\"name\":\"stress-t${i}-${TS}\",\"max_queues\":100}") - 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]:0:20}..." - if [[ -z "${TENANT_AK[$i]}" ]]; then - echo " ⛔ ОШИБКА создания тенанта ${i}: $R" - exit 1 - fi -done - -# Сохраняем в файлы — для subshell -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 - -T1_AK="${TENANT_AK[1]}"; T1_SK="${TENANT_SK[1]}"; T1_ID="${TENANT_ID[1]}" - -# Очереди -STRESS_Q="stress-conc-${TS}" -for i in $(seq 1 $TENANT_COUNT); do - R=$(sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" create-queue --queue-name "$STRESS_Q") -done -STRESS_QURL=$(qurl "$T1_ID" "$STRESS_Q") - -BURST_Q="stress-burst-${TS}" -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BURST_Q") -BURST_QURL=$(qurl "$T1_ID" "$BURST_Q") - -echo " Очередь conc: $STRESS_Q (для $TENANT_COUNT тенантов)" -echo " Очередь burst: $BURST_Q" -echo " Подготовка завершена за $(($(date +%s) - STARTED_AT))s" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 2. КОНКУРЕНТНАЯ ОТПРАВКА — 50 × 20 = 1000 сообщений -# ═══════════════════════════════════════════════════════════ -TOTAL_MSGS=$((CONCURRENT_WORKERS * MESSAGES_PER_WORKER)) -echo "═══ 2. Конкурентная отправка: ${CONCURRENT_WORKERS} воркеров × ${MESSAGES_PER_WORKER} = ${TOTAL_MSGS} сообщений ═══" -SEC2_START=$(date +%s) - -for w in $(seq 1 $CONCURRENT_WORKERS); do - ( - ok=0; fail=0 - for m in $(seq 1 $MESSAGES_PER_WORKER); do - R=$(sqs "$T1_AK" "$T1_SK" send-message \ - --queue-url "$STRESS_QURL" \ - --message-body "w${w}-m${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" - # Прогресс: каждый 10-й воркер печатает - if (( w % 10 == 0 )); then printf "[W${w}:${ok}ok] " >&2; fi - ) & -done -echo " ⏳ Отправка идёт... (прогресс каждые 10 воркеров)" -wait -echo "" - -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 -SEC2_DUR=$(($(date +%s) - SEC2_START)) -echo " Результат: ${SEND_OK} ok, ${SEND_FAIL} fail за ${SEC2_DUR}s (~$((SEND_OK / (SEC2_DUR + 1))) msg/s)" -check_eq "Все ${TOTAL_MSGS} сообщений отправлены" "$SEND_OK" "$TOTAL_MSGS" - -sleep 2 -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 " GetQueueAttributes: ~${APPROX} в очереди" -check_eq "Количество в очереди = $TOTAL_MSGS" "${APPROX:-0}" "$TOTAL_MSGS" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 3. КОНКУРЕНТНОЕ ЧТЕНИЕ — гонка за 1000 сообщений -# ═══════════════════════════════════════════════════════════ -echo "═══ 3. Конкурентное чтение: ${CONCURRENT_WORKERS} воркеров забирают ${TOTAL_MSGS} сообщений ═══" -SEC3_START=$(date +%s) - -for w in $(seq 1 $CONCURRENT_WORKERS); do - ( - received=0; deleted=0; empty_rounds=0 - while [[ $empty_rounds -lt 8 ]]; do - R=$(sqs "$T1_AK" "$T1_SK" receive-message \ - --queue-url "$STRESS_QURL" \ - --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 - sqs "$T1_AK" "$T1_SK" delete-message \ - --queue-url "$STRESS_QURL" --receipt-handle "$handle" >/dev/null 2>&1 - deleted=$((deleted + 1)) - done <<< "$HANDLES" - done - echo "${received}:${deleted}" > "${TMPDIR_STRESS}/recv_w${w}.result" - if (( w % 10 == 0 )); then printf "[R${w}:${received}r/${deleted}d] " >&2; fi - ) & -done -echo " ⏳ Чтение идёт... (прогресс каждые 10 воркеров)" -wait -echo "" - -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 -SEC3_DUR=$(($(date +%s) - SEC3_START)) -echo " Результат: прочитано=${RECV_TOTAL}, удалено=${DEL_TOTAL} за ${SEC3_DUR}s" -check_ge "Прочитано >= $TOTAL_MSGS" "$RECV_TOTAL" "$TOTAL_MSGS" - -sleep 2 -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" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 4. MULTI-TENANT ИЗОЛЯЦИЯ — 5 тенантов × 50 msg одновременно -# ═══════════════════════════════════════════════════════════ -MSGS_PER_TENANT=30 -echo "═══ 4. Multi-tenant изоляция: ${TENANT_COUNT} тенантов × ${MSGS_PER_TENANT} msg ═══" -SEC4_START=$(date +%s) - -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 "$tak" "$tsk" send-message --queue-url "$my_qurl" \ - --message-body "tenant-${i}-iso-${m}" 2>&1) - if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi - done - echo "$ok" > "${TMPDIR_STRESS}/tenant_send_${i}.result" - printf "[T${i}:${ok}sent] " >&2 - ) & -done -echo " ⏳ Тенанты отправляют..." -wait -echo "" - -# Чтение и проверка изоляции -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 "$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) - if [[ "$MSG_COUNT" -eq 0 ]]; then empty_rounds=$((empty_rounds + 1)); continue; fi - empty_rounds=0 - 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 "$tak" "$tsk" delete-message --queue-url "$my_qurl" --receipt-handle "$handle" >/dev/null 2>&1 - done <<< "$HANDLES" - done - echo "${own}:${foreign}" > "${TMPDIR_STRESS}/tenant_recv_${i}.result" - printf "[T${i}:own=${own},foreign=${foreign}] " >&2 - ) & -done -echo " ⏳ Тенанты читают..." -wait -echo "" - -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 -SEC4_DUR=$(($(date +%s) - SEC4_START)) -echo " Итого: своих=$ALL_OWN, чужих=$ALL_FOREIGN за ${SEC4_DUR}s" -check_eq "Чужих сообщений: 0" "$ALL_FOREIGN" "0" -check_ge "Свои сообщения получены" "$ALL_OWN" "$((TENANT_COUNT * MSGS_PER_TENANT))" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 5. BURST — 100 сообщений одновременно -# ═══════════════════════════════════════════════════════════ -echo "═══ 5. Burst: ${BURST_SIZE} сообщений одновременно ═══" -SEC5_START=$(date +%s) - -for b in $(seq 1 $BURST_SIZE); do - ( - 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" - ) & - # Прогресс пачками по 25 - if (( b % 25 == 0 )); then printf "[${b}/${BURST_SIZE}] " >&2; fi -done -echo " ⏳ Burst отправка..." -wait -echo "" - -BURST_OK=0 -for b in $(seq 1 $BURST_SIZE); do - [[ -f "${TMPDIR_STRESS}/burst_${b}.result" ]] && BURST_OK=$((BURST_OK + $(cat "${TMPDIR_STRESS}/burst_${b}.result"))) -done -SEC5_DUR=$(($(date +%s) - SEC5_START)) -echo " Результат: ${BURST_OK}/${BURST_SIZE} за ${SEC5_DUR}s" -check_ge "Burst >= 90%" "$BURST_OK" "$((BURST_SIZE * 90 / 100))" - -sleep 3 -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}" -check_eq "Burst все в очереди" "${BURST_APPROX:-0}" "$BURST_OK" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 6. LONG-POLLING + параллельные producer'ы -# ═══════════════════════════════════════════════════════════ -echo "═══ 6. Long-polling: consumer WaitTime=10s + 5 producers ═══" -SEC6_START=$(date +%s) - -LONGPOLL_Q="stress-longpoll-${TS}" -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$LONGPOLL_Q" \ - --attributes '{"ReceiveMessageWaitTimeSeconds":"10"}') -LONGPOLL_QURL=$(qurl "$T1_ID" "$LONGPOLL_Q") - -# Consumer: читает с long-poll, считает сколько получит за 30s -( - end=$((SECONDS + 20)) - total_recv=0 - while [[ $SECONDS -lt $end ]]; do - R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$LONGPOLL_QURL" \ - --max-number-of-messages 10 --wait-time-seconds 10 2>&1) - cnt=$(echo "$R" | grep -c '"MessageId"' || true) - total_recv=$((total_recv + cnt)) - if (( total_recv % 10 == 0 && total_recv > 0 )); then printf "[LP-recv:${total_recv}] " >&2; fi - done - echo "$total_recv" > "${TMPDIR_STRESS}/longpoll_recv.result" -) & - -# Producers: начинают через 3s, каждый отправляет 20 msg с паузами -sleep 3 -for p in $(seq 1 5); do - ( - ok=0 - for m in $(seq 1 20); do - R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$LONGPOLL_QURL" \ - --message-body "longpoll-p${p}-m${m}" 2>&1) - if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi - sleep 0.2 - done - echo "$ok" > "${TMPDIR_STRESS}/longpoll_prod_${p}.result" - printf "[LP-prod${p}:${ok}] " >&2 - ) & -done -echo " ⏳ Long-poll: consumer ждёт, producers отправляют (20s)..." -wait -echo "" - -LP_SENT=0 -for p in $(seq 1 5); do - [[ -f "${TMPDIR_STRESS}/longpoll_prod_${p}.result" ]] && LP_SENT=$((LP_SENT + $(cat "${TMPDIR_STRESS}/longpoll_prod_${p}.result"))) -done -LP_RECV=$(cat "${TMPDIR_STRESS}/longpoll_recv.result" 2>/dev/null || echo 0) -SEC6_DUR=$(($(date +%s) - SEC6_START)) -echo " Producers отправили: $LP_SENT, consumer получил: $LP_RECV за ${SEC6_DUR}s" -check_ge "Long-poll: получено >= 50% отправленных" "$LP_RECV" "$((LP_SENT / 2))" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 7. ДВОЙНОЕ УДАЛЕНИЕ — race на receipt handle -# ═══════════════════════════════════════════════════════════ -echo "═══ 7. Двойное удаление: 2 воркера гонятся за одно сообщение ═══" - -RACE_Q="stress-race-${TS}" -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$RACE_Q") -RACE_QURL=$(qurl "$T1_ID" "$RACE_Q") - -# Отправляем 20 сообщений -for m in $(seq 1 20); do - sqs "$T1_AK" "$T1_SK" send-message --queue-url "$RACE_QURL" \ - --message-body "race-${m}" >/dev/null 2>&1 -done -echo " Отправлено 20 сообщений" - -# VisibilityTimeout=0 — чтобы оба воркера получали одни сообщения -RACE_DOUBLE_DEL=0 -RACE_OK_DEL=0 -for round in $(seq 1 10); do - # Два воркера одновременно получают - R1=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$RACE_QURL" \ - --max-number-of-messages 1 --visibility-timeout 30 2>&1) - HANDLE=$(echo "$R1" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1) - if [[ -z "$HANDLE" ]]; then continue; fi - - # Два параллельных delete - D1=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$RACE_QURL" --receipt-handle "$HANDLE" 2>&1) & - PID1=$! - D2=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$RACE_QURL" --receipt-handle "$HANDLE" 2>&1) & - PID2=$! - wait $PID1 $PID2 - RACE_OK_DEL=$((RACE_OK_DEL + 1)) - printf "[race-${round}] " >&2 -done -echo "" -echo " Раундов race: ${RACE_OK_DEL}/10" -# Главное: сервер НЕ упал -R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes --queue-url "$RACE_QURL" \ - --attribute-names ApproximateNumberOfMessages 2>&1) -check "Сервер жив после double-delete" "$R" "ApproximateNumberOfMessages" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 8. BATCH-ОПЕРАЦИИ конкурентно -# ═══════════════════════════════════════════════════════════ -echo "═══ 8. Batch-операции: ${BATCH_WORKERS} воркеров SendMessageBatch + DeleteMessageBatch ═══" -SEC8_START=$(date +%s) - -BATCH_Q="stress-batch-${TS}" -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BATCH_Q") -BATCH_QURL=$(qurl "$T1_ID" "$BATCH_Q") - -# Параллельные SendMessageBatch (10 msg per batch × 10 workers = 100 msg) -for w in $(seq 1 $BATCH_WORKERS); do - ( - # Формируем batch из 10 записей - ENTRIES="" - for e in $(seq 1 10); do - [[ -n "$ENTRIES" ]] && ENTRIES="${ENTRIES}," - ENTRIES="${ENTRIES}{\"Id\":\"${w}-${e}\",\"MessageBody\":\"batch-w${w}-e${e}\"}" - done - R=$(sqs "$T1_AK" "$T1_SK" send-message-batch --queue-url "$BATCH_QURL" \ - --entries "[${ENTRIES}]" 2>&1) - ok=$(echo "$R" | grep -c '"MessageId"' || true) - echo "$ok" > "${TMPDIR_STRESS}/batch_send_${w}.result" - printf "[B-send${w}:${ok}] " >&2 - ) & -done -echo " ⏳ SendMessageBatch..." -wait -echo "" - -BATCH_SENT=0 -for w in $(seq 1 $BATCH_WORKERS); do - [[ -f "${TMPDIR_STRESS}/batch_send_${w}.result" ]] && BATCH_SENT=$((BATCH_SENT + $(cat "${TMPDIR_STRESS}/batch_send_${w}.result"))) -done -echo " Batch отправлено: ${BATCH_SENT}" -check_ge "Batch: отправлено >= 80" "$BATCH_SENT" "80" - -# Параллельные ReceiveMessage + DeleteMessageBatch -BATCH_DELETED=0 -for w in $(seq 1 5); do - ( - del=0; empty=0 - while [[ $empty -lt 3 ]]; do - R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$BATCH_QURL" \ - --max-number-of-messages 10 --wait-time-seconds 1 2>&1) - HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+') - if [[ -z "$HANDLES" ]]; then empty=$((empty + 1)); continue; fi - empty=0 - # Формируем batch delete - ENTRIES="" - idx=0 - while IFS= read -r handle; do - [[ -z "$handle" ]] && continue - [[ -n "$ENTRIES" ]] && ENTRIES="${ENTRIES}," - ENTRIES="${ENTRIES}{\"Id\":\"${idx}\",\"ReceiptHandle\":\"${handle}\"}" - idx=$((idx + 1)) - done <<< "$HANDLES" - if [[ -n "$ENTRIES" ]]; then - DR=$(sqs "$T1_AK" "$T1_SK" delete-message-batch --queue-url "$BATCH_QURL" \ - --entries "[${ENTRIES}]" 2>&1) - ok_del=$(echo "$DR" | grep -c '"Id"' || true) - del=$((del + ok_del)) - fi - done - echo "$del" > "${TMPDIR_STRESS}/batch_del_${w}.result" - printf "[B-del${w}:${del}] " >&2 - ) & -done -echo " ⏳ DeleteMessageBatch..." -wait -echo "" - -BATCH_DEL_TOTAL=0 -for w in $(seq 1 5); do - [[ -f "${TMPDIR_STRESS}/batch_del_${w}.result" ]] && BATCH_DEL_TOTAL=$((BATCH_DEL_TOTAL + $(cat "${TMPDIR_STRESS}/batch_del_${w}.result"))) -done -SEC8_DUR=$(($(date +%s) - SEC8_START)) -echo " Batch удалено: $BATCH_DEL_TOTAL за ${SEC8_DUR}s" -check_ge "Batch: удалено >= 80%" "$BATCH_DEL_TOTAL" "$((BATCH_SENT * 80 / 100))" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 9. МНОГО ОЧЕРЕДЕЙ — 30 очередей на тенанта -# ═══════════════════════════════════════════════════════════ -echo "═══ 9. Queue-flood: ${QUEUE_FLOOD_COUNT} очередей, send+receive в каждую ═══" -SEC9_START=$(date +%s) - -# Создаём 30 очередей параллельно -for q in $(seq 1 $QUEUE_FLOOD_COUNT); do - ( - qname="stress-flood-${q}-${TS}" - my_qurl=$(qurl "$T1_ID" "$qname") - # Создаём - R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$qname" 2>&1) - if ! echo "$R" | grep -q "QueueUrl"; then echo "0" > "${TMPDIR_STRESS}/flood_${q}.result"; exit 0; fi - # Отправляем 5 msg - for m in $(seq 1 5); do - sqs "$T1_AK" "$T1_SK" send-message --queue-url "$my_qurl" \ - --message-body "flood-q${q}-m${m}" >/dev/null 2>&1 - done - # Читаем 1 - R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$my_qurl" \ - --max-number-of-messages 1 --wait-time-seconds 1 2>&1) - if echo "$R" | grep -q "MessageId"; then echo "1" > "${TMPDIR_STRESS}/flood_${q}.result" - else echo "0" > "${TMPDIR_STRESS}/flood_${q}.result"; fi - if (( q % 10 == 0 )); then printf "[Q${q}] " >&2; fi - ) & -done -echo " ⏳ Создание и тест ${QUEUE_FLOOD_COUNT} очередей..." -wait -echo "" - -FLOOD_OK=0 -for q in $(seq 1 $QUEUE_FLOOD_COUNT); do - [[ -f "${TMPDIR_STRESS}/flood_${q}.result" ]] && FLOOD_OK=$((FLOOD_OK + $(cat "${TMPDIR_STRESS}/flood_${q}.result"))) -done -SEC9_DUR=$(($(date +%s) - SEC9_START)) -echo " Успешных: ${FLOOD_OK}/${QUEUE_FLOOD_COUNT} за ${SEC9_DUR}s" -check_ge "Queue-flood: >= 90% очередей работают" "$FLOOD_OK" "$((QUEUE_FLOOD_COUNT * 90 / 100))" - -# Проверяем ListQueues — должно быть >= 30 flood-очередей -R=$(sqs "$T1_AK" "$T1_SK" list-queues --queue-name-prefix "stress-flood" 2>&1) -LISTED=$(echo "$R" | grep -c "stress-flood" || true) -echo " ListQueues видит: $LISTED очередей" -check_ge "ListQueues >= ${QUEUE_FLOOD_COUNT}" "$LISTED" "$QUEUE_FLOOD_COUNT" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 10. MEMORY CHECK — RSS до нагрузки -# ═══════════════════════════════════════════════════════════ -echo "═══ 10. Memory check — запоминаем RSS ═══" -RSS_BEFORE=$(get_pod_rss_mb) -echo " RSS до тяжёлой фазы: ${RSS_BEFORE} MB" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 11. KILL POD + восстановление из Redis -# ═══════════════════════════════════════════════════════════ -echo "═══ 11. Kill pod → восстановление из Redis ═══" - -# Перед kill — отправляем в burst-очередь ещё 20 msg -EXTRA=20 -for m in $(seq 1 $EXTRA); do - sqs "$T1_AK" "$T1_SK" send-message --queue-url "$BURST_QURL" \ - --message-body "pre-kill-${m}-${TS}" >/dev/null 2>&1 - if (( m % 5 == 0 )); then printf "[pre-kill:${m}] " >&2; fi -done -echo "" - -R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes --queue-url "$BURST_QURL" \ - --attribute-names ApproximateNumberOfMessages 2>&1) -BEFORE_KILL=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+') -echo " Сообщений в burst до kill: ${BEFORE_KILL:-?}" - -kill_pod -echo " Ждём рестарт..." -wait_for_pod_ready -wait_for_healthy - -sleep 3 -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}" -check_eq "Данные восстановлены из Redis" "${AFTER_KILL:-0}" "${BEFORE_KILL}" - -R=$(admin_api GET "/admin/tenants") -TC=$(echo "$R" | grep -c "stress-t1-${TS}" || true) -check_ge "Тенанты восстановлены" "$TC" "1" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 12. REDIS DISCONNECT / RECONNECT -# ═══════════════════════════════════════════════════════════ -echo "═══ 12. Redis disconnect simulation ═══" - -cat <<'NETPOL' | kubectl apply -n "$NAMESPACE" -f - 2>&1 | head -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 applied — Redis заблокирован" -sleep 5 - -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 при потере Redis: $R_CODE" -TOTAL=$((TOTAL + 1)) -if [[ "$R_CODE" =~ ^[2345][0-9][0-9]$ ]]; then - PASS=$((PASS + 1)); echo " ✅ Сервис отвечает (HTTP $R_CODE)" -else - FAIL=$((FAIL + 1)); echo " ❌ Сервис не отвечает (HTTP $R_CODE)" -fi - -kubectl delete networkpolicy stress-test-block-redis -n "$NAMESPACE" 2>&1 | head -1 -echo " NetworkPolicy удалена — Redis доступен" -sleep 5 - -R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$BURST_QURL" \ - --message-body "after-redis-recovery-${TS}" 2>&1) -check "Сервис работает после Redis reconnect" "$R" "MessageId" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 13. СМЕШАННАЯ НАГРУЗКА — send + receive + delete + getattr (20s) -# ═══════════════════════════════════════════════════════════ -echo "═══ 13. Смешанная нагрузка: 10 senders + 10 receivers + 3 getattr × ${MIXED_DURATION}s ═══" -SEC13_START=$(date +%s) - -MIXED_Q="stress-mixed-${TS}" -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$MIXED_Q") -MIXED_QURL=$(qurl "$T1_ID" "$MIXED_Q") - -# Предзагрузка 100 msg -for m in $(seq 1 30); do - sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MIXED_QURL" \ - --message-body "mixed-init-${m}" >/dev/null 2>&1 - if (( m % 20 == 0 )); then printf "[init:${m}] " >&2; fi -done -echo "" -echo " Начальная загрузка: 30 msg" - -# 10 Отправителей -for w in $(seq 1 5); do - ( - end=$((SECONDS + MIXED_DURATION)); ok=0 - while [[ $SECONDS -lt $end ]]; do - R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MIXED_QURL" \ - --message-body "mixed-s${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 - -# 10 Читателей-удалителей -for w in $(seq 1 5); do - ( - end=$((SECONDS + MIXED_DURATION)); recv=0 - while [[ $SECONDS -lt $end ]]; do - 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 "$T1_AK" "$T1_SK" delete-message --queue-url "$MIXED_QURL" \ - --receipt-handle "$handle" >/dev/null 2>&1 - recv=$((recv + 1)) - done <<< "$HANDLES" - done - echo "$recv" > "${TMPDIR_STRESS}/mixed_recv_${w}.result" - ) & -done - -# 3 GetQueueAttributes -for w in $(seq 1 2); do - ( - end=$((SECONDS + MIXED_DURATION)); ok=0; fail=0 - while [[ $SECONDS -lt $end ]]; do - 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)); else fail=$((fail + 1)); fi - sleep 0.5 - done - echo "${ok}:${fail}" > "${TMPDIR_STRESS}/mixed_attr_${w}.result" - ) & -done - -# Прогресс каждые 5 сек -( - elapsed=0 - while [[ $elapsed -lt $MIXED_DURATION ]]; do - sleep 5 - elapsed=$((elapsed + 5)) - printf "[mixed: ${elapsed}/${MIXED_DURATION}s] " >&2 - done -) & -PROGRESS_PID=$! - -echo " ⏳ Смешанная нагрузка ${MIXED_DURATION}s..." -wait -echo "" - -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 -SEC13_DUR=$(($(date +%s) - SEC13_START)) -echo " Отправлено: $MIXED_SENT, прочитано+удалено: $MIXED_RECV | Attr: ok=$ATTR_OK fail=$ATTR_FAIL | ${SEC13_DUR}s" -check_ge "Mixed: отправлено > 0" "$MIXED_SENT" "1" -check_ge "Mixed: прочитано > 0" "$MIXED_RECV" "1" -check_eq "Mixed: GetQueueAttributes 0 сбоев" "$ATTR_FAIL" "0" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 14. MEMORY CHECK — после нагрузки -# ═══════════════════════════════════════════════════════════ -echo "═══ 14. Memory check — RSS после нагрузки ═══" -RSS_AFTER=$(get_pod_rss_mb) -echo " RSS до: ${RSS_BEFORE} MB, после: ${RSS_AFTER} MB" -if [[ "$RSS_BEFORE" -gt 0 && "$RSS_AFTER" -gt 0 ]]; then - RSS_GROWTH=$((RSS_AFTER - RSS_BEFORE)) - echo " Рост: ${RSS_GROWTH} MB" - # Допускаем рост до 100 MB — выше уже подозрительно - check_le "Рост RSS <= 100MB" "$RSS_GROWTH" "100" -else - echo " ⚠️ Не удалось измерить RSS (возможно /proc недоступен)" - TOTAL=$((TOTAL + 1)); PASS=$((PASS + 1)) - echo " ✅ Memory check пропущен (нет доступа к /proc)" -fi -echo "" - -# ═══════════════════════════════════════════════════════════ -# 15. MULTI-KILL: kill → up → kill → up -# ═══════════════════════════════════════════════════════════ -echo "═══ 15. Multi-kill: 3 рестарта подряд ═══" -SEC15_START=$(date +%s) - -# Отправляем маркерное сообщение -MARKER_Q="stress-marker-${TS}" -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$MARKER_Q") -MARKER_QURL=$(qurl "$T1_ID" "$MARKER_Q") -sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MARKER_QURL" \ - --message-body "marker-survive-3-kills-${TS}" >/dev/null 2>&1 - -for k in 1 2 3; do - echo " --- Kill #${k} ---" - kill_pod - wait_for_pod_ready - wait_for_healthy - echo " Kill #${k}: под восстановлен ✓" -done - -# Маркер выжил? -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$MARKER_QURL" \ - --max-number-of-messages 1 --wait-time-seconds 2 2>&1) -SEC15_DUR=$(($(date +%s) - SEC15_START)) -echo " 3 рестарта за ${SEC15_DUR}s" -check "Маркер выжил 3 рестарта" "$R" "marker-survive-3-kills" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 16. CLEANUP -# ═══════════════════════════════════════════════════════════ -echo "═══ 16. Cleanup ═══" -echo " Удаляю очереди..." - -# Основные очереди -for i in $(seq 1 $TENANT_COUNT); do - tak=$(cat "${TMPDIR_STRESS}/tak_${i}" 2>/dev/null) - tsk=$(cat "${TMPDIR_STRESS}/tsk_${i}" 2>/dev/null) - tid=$(cat "${TMPDIR_STRESS}/tid_${i}" 2>/dev/null) - [[ -n "$tak" ]] && sqs "$tak" "$tsk" delete-queue --queue-url "${BASE_URL}/${tid}/${STRESS_Q}" >/dev/null 2>&1 -done -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 -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$LONGPOLL_QURL" >/dev/null 2>&1 -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$RACE_QURL" >/dev/null 2>&1 -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$BATCH_QURL" >/dev/null 2>&1 -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$MARKER_QURL" >/dev/null 2>&1 - -# Flood-очереди -for q in $(seq 1 $QUEUE_FLOOD_COUNT); do - sqs "$T1_AK" "$T1_SK" delete-queue \ - --queue-url "$(qurl "$T1_ID" "stress-flood-${q}-${TS}")" >/dev/null 2>&1 & -done -wait - -echo " Очереди удалены" - -TOTAL_DUR=$(($(date +%s) - STARTED_AT)) -echo "" -echo "╔═══════════════════════════════════════════════════════════════════╗" -echo "║ ИТОГО: ${PASS}/${TOTAL} ✅ ${FAIL}/${TOTAL} ❌ ║" -echo "║ Время: ${TOTAL_DUR}s ║" -echo "╚═══════════════════════════════════════════════════════════════════╝" - -if [[ $FAIL -gt 0 ]]; then exit 1; fi diff --git a/tests/stress_test_v2.sh b/tests/stress_test_v2.sh deleted file mode 100644 index bb4d139..0000000 --- a/tests/stress_test_v2.sh +++ /dev/null @@ -1,966 +0,0 @@ -#!/bin/bash -# stress_test.sh — ЖЁСТКИЙ стресс-тест shared-sqs -# Created: 2026-04-11 -# Updated: 2026-04-11 — v2: масштаб, long-poll, race, batch, 50 очередей, memory, multi-kill -# Покрывает: -# 1. Подготовка (тенанты, очереди) -# 2. Параллельная отправка — 50 воркеров × 20 msg = 1000 сообщений -# 3. Параллельное чтение + удаление (гонка) -# 4. Multi-tenant изоляция (5 тенантов одновременно) -# 5. Burst — 100 сообщений одновременно -# 6. Long-polling + параллельные producer'ы -# 7. Двойное удаление (race на receipt handle) -# 8. Batch-операции конкурентно (SendMessageBatch + DeleteMessageBatch) -# 9. Много очередей — 30 очередей на тенанта -# 10. Kill pod + восстановление из Redis -# 11. Redis disconnect / reconnect -# 12. Смешанная нагрузка (send+recv+delete+getattr, 20s) -# 13. Memory leak check (RSS до/после нагрузки) -# 14. Multi-kill: kill → восстановление → kill → восстановление -# 15. Cleanup -# Запуск: 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" -POD_LABEL="app.kubernetes.io/name=shared-sqs" - -# Параметры нагрузки — ЖЁСТКИЕ -CONCURRENT_WORKERS=50 # параллельных воркеров -MESSAGES_PER_WORKER=20 # сообщений на воркер (итого 1000) -BURST_SIZE=100 # размер burst-пакета -TENANT_COUNT=5 # тенантов для multi-tenant теста -QUEUE_FLOOD_COUNT=30 # очередей на тенанта -MIXED_DURATION=20 # длительность смешанной нагрузки (сек) -BATCH_WORKERS=10 # воркеров для batch-операций - -PASS=0 -FAIL=0 -TOTAL=0 -TMPDIR_STRESS=$(mktemp -d /tmp/sqs_stress_XXXXX) - -# ═══════════════════════════════════════════ -# ХЕЛПЕРЫ -# ═══════════════════════════════════════════ - -json_field() { - python3 -c "import sys,json; print(json.load(sys.stdin).get('$1',''))" 2>/dev/null -} - -# progress_dot — печатает точку без переноса строки (прогресс) -progress_dot() { - printf "." >&2 -} - -# progress_done — завершает строку прогресса -progress_done() { - echo "" >&2 -} - -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 -} - -check_le() { - local name="$1" got="$2" want="$3" - TOTAL=$((TOTAL + 1)) - if [[ "$got" -le "$want" ]]; then - PASS=$((PASS + 1)) - echo " ✅ $name ($got <= $want)" - else - FAIL=$((FAIL + 1)) - echo " ❌ $name: ожидалось <=$want, получено=$got" - fi -} - -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() { - 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 -} - -qurl() { echo "${BASE_URL}/${1}/${2}"; } - -wait_for_pod_ready() { - local max_wait=120 waited=0 - echo " ⏳ Ожидание пода (max ${max_wait}s)..." - while [[ $waited -lt $max_wait ]]; do - local ready - ready=$(kubectl get pods -n "$NAMESPACE" -l "$POD_LABEL" \ - -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)) - printf "." >&2 - done - echo "" - echo " ⚠️ Под не готов после ${max_wait}s" - return 1 -} - -wait_for_healthy() { - local max_wait=120 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)) - printf "." >&2 - done - echo "" - echo " ⚠️ Сервис не отвечает после ${max_wait}s" - return 1 -} - -# get_pod_rss_mb — RSS памяти текущего пода в MB -get_pod_rss_mb() { - local pod - pod=$(kubectl get pods -n "$NAMESPACE" -l "$POD_LABEL" \ - -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) - # kubectl top может не работать — fallback на /proc - local rss_kb - rss_kb=$(kubectl exec -n "$NAMESPACE" "$pod" -- cat /proc/1/status 2>/dev/null \ - | grep VmRSS | awk '{print $2}') - if [[ -n "$rss_kb" ]]; then - echo $((rss_kb / 1024)) - else - echo "0" - fi -} - -kill_pod() { - local pod - pod=$(kubectl get pods -n "$NAMESPACE" -l "$POD_LABEL" \ - -o jsonpath='{.items[0].metadata.name}' 2>/dev/null) - echo " 🔪 Kill: $pod" - kubectl delete pod "$pod" -n "$NAMESPACE" --grace-period=0 --force 2>&1 | head -1 -} - -cleanup() { rm -rf "$TMPDIR_STRESS" 2>/dev/null; } -trap cleanup EXIT - -STARTED_AT=$(date +%s) - -echo "╔═══════════════════════════════════════════════════════════════════╗" -echo "║ shared-sqs STRESS TEST v2 — $(date '+%Y-%m-%d %H:%M:%S') ║" -echo "║ Endpoint: ${BASE_URL} ║" -echo "║ Workers: ${CONCURRENT_WORKERS} Msgs/worker: ${MESSAGES_PER_WORKER} Burst: ${BURST_SIZE} Tenants: ${TENANT_COUNT} ║" -echo "║ Queue-flood: ${QUEUE_FLOOD_COUNT} Batch-workers: ${BATCH_WORKERS} Mixed: ${MIXED_DURATION}s ║" -echo "╚═══════════════════════════════════════════════════════════════════╝" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 1. ПОДГОТОВКА — тенанты и очереди -# ═══════════════════════════════════════════════════════════ -echo "═══ 1. Подготовка — создаём $TENANT_COUNT тенантов ═══" - -declare -a TENANT_AK TENANT_SK TENANT_ID -for i in $(seq 1 $TENANT_COUNT); do - R=$(admin_api POST "/admin/tenants" "{\"name\":\"stress-t${i}-${TS}\",\"max_queues\":100}") - 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]:0:20}..." - if [[ -z "${TENANT_AK[$i]}" ]]; then - echo " ⛔ ОШИБКА создания тенанта ${i}: $R" - exit 1 - fi -done - -# Сохраняем в файлы — для subshell -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 - -T1_AK="${TENANT_AK[1]}"; T1_SK="${TENANT_SK[1]}"; T1_ID="${TENANT_ID[1]}" - -# Очереди -STRESS_Q="stress-conc-${TS}" -for i in $(seq 1 $TENANT_COUNT); do - R=$(sqs "${TENANT_AK[$i]}" "${TENANT_SK[$i]}" create-queue --queue-name "$STRESS_Q") -done -STRESS_QURL=$(qurl "$T1_ID" "$STRESS_Q") - -BURST_Q="stress-burst-${TS}" -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BURST_Q") -BURST_QURL=$(qurl "$T1_ID" "$BURST_Q") - -echo " Очередь conc: $STRESS_Q (для $TENANT_COUNT тенантов)" -echo " Очередь burst: $BURST_Q" -echo " Подготовка завершена за $(($(date +%s) - STARTED_AT))s" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 2. КОНКУРЕНТНАЯ ОТПРАВКА — 50 × 20 = 1000 сообщений -# ═══════════════════════════════════════════════════════════ -TOTAL_MSGS=$((CONCURRENT_WORKERS * MESSAGES_PER_WORKER)) -echo "═══ 2. Конкурентная отправка: ${CONCURRENT_WORKERS} воркеров × ${MESSAGES_PER_WORKER} = ${TOTAL_MSGS} сообщений ═══" -SEC2_START=$(date +%s) - -for w in $(seq 1 $CONCURRENT_WORKERS); do - ( - ok=0; fail=0 - for m in $(seq 1 $MESSAGES_PER_WORKER); do - R=$(sqs "$T1_AK" "$T1_SK" send-message \ - --queue-url "$STRESS_QURL" \ - --message-body "w${w}-m${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" - # Прогресс: каждый 10-й воркер печатает - if (( w % 10 == 0 )); then printf "[W${w}:${ok}ok] " >&2; fi - ) & -done -echo " ⏳ Отправка идёт... (прогресс каждые 10 воркеров)" -wait -echo "" - -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 -SEC2_DUR=$(($(date +%s) - SEC2_START)) -echo " Результат: ${SEND_OK} ok, ${SEND_FAIL} fail за ${SEC2_DUR}s (~$((SEND_OK / (SEC2_DUR + 1))) msg/s)" -check_eq "Все ${TOTAL_MSGS} сообщений отправлены" "$SEND_OK" "$TOTAL_MSGS" - -sleep 2 -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 " GetQueueAttributes: ~${APPROX} в очереди" -check_eq "Количество в очереди = $TOTAL_MSGS" "${APPROX:-0}" "$TOTAL_MSGS" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 3. КОНКУРЕНТНОЕ ЧТЕНИЕ — гонка за 1000 сообщений -# ═══════════════════════════════════════════════════════════ -echo "═══ 3. Конкурентное чтение: ${CONCURRENT_WORKERS} воркеров забирают ${TOTAL_MSGS} сообщений ═══" -SEC3_START=$(date +%s) - -for w in $(seq 1 $CONCURRENT_WORKERS); do - ( - received=0; deleted=0; empty_rounds=0 - while [[ $empty_rounds -lt 8 ]]; do - R=$(sqs "$T1_AK" "$T1_SK" receive-message \ - --queue-url "$STRESS_QURL" \ - --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 - sqs "$T1_AK" "$T1_SK" delete-message \ - --queue-url "$STRESS_QURL" --receipt-handle "$handle" >/dev/null 2>&1 - deleted=$((deleted + 1)) - done <<< "$HANDLES" - done - echo "${received}:${deleted}" > "${TMPDIR_STRESS}/recv_w${w}.result" - if (( w % 10 == 0 )); then printf "[R${w}:${received}r/${deleted}d] " >&2; fi - ) & -done -echo " ⏳ Чтение идёт... (прогресс каждые 10 воркеров)" -wait -echo "" - -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 -SEC3_DUR=$(($(date +%s) - SEC3_START)) -echo " Результат: прочитано=${RECV_TOTAL}, удалено=${DEL_TOTAL} за ${SEC3_DUR}s" -check_ge "Прочитано >= $TOTAL_MSGS" "$RECV_TOTAL" "$TOTAL_MSGS" - -sleep 2 -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" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 4. MULTI-TENANT ИЗОЛЯЦИЯ — 5 тенантов × 50 msg одновременно -# ═══════════════════════════════════════════════════════════ -MSGS_PER_TENANT=50 -echo "═══ 4. Multi-tenant изоляция: ${TENANT_COUNT} тенантов × ${MSGS_PER_TENANT} msg ═══" -SEC4_START=$(date +%s) - -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 "$tak" "$tsk" send-message --queue-url "$my_qurl" \ - --message-body "tenant-${i}-iso-${m}" 2>&1) - if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi - done - echo "$ok" > "${TMPDIR_STRESS}/tenant_send_${i}.result" - printf "[T${i}:${ok}sent] " >&2 - ) & -done -echo " ⏳ Тенанты отправляют..." -wait -echo "" - -# Чтение и проверка изоляции -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 "$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) - if [[ "$MSG_COUNT" -eq 0 ]]; then empty_rounds=$((empty_rounds + 1)); continue; fi - empty_rounds=0 - 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 "$tak" "$tsk" delete-message --queue-url "$my_qurl" --receipt-handle "$handle" >/dev/null 2>&1 - done <<< "$HANDLES" - done - echo "${own}:${foreign}" > "${TMPDIR_STRESS}/tenant_recv_${i}.result" - printf "[T${i}:own=${own},foreign=${foreign}] " >&2 - ) & -done -echo " ⏳ Тенанты читают..." -wait -echo "" - -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 -SEC4_DUR=$(($(date +%s) - SEC4_START)) -echo " Итого: своих=$ALL_OWN, чужих=$ALL_FOREIGN за ${SEC4_DUR}s" -check_eq "Чужих сообщений: 0" "$ALL_FOREIGN" "0" -check_ge "Свои сообщения получены" "$ALL_OWN" "$((TENANT_COUNT * MSGS_PER_TENANT))" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 5. BURST — 100 сообщений одновременно -# ═══════════════════════════════════════════════════════════ -echo "═══ 5. Burst: ${BURST_SIZE} сообщений одновременно ═══" -SEC5_START=$(date +%s) - -for b in $(seq 1 $BURST_SIZE); do - ( - 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" - ) & - # Прогресс пачками по 25 - if (( b % 25 == 0 )); then printf "[${b}/${BURST_SIZE}] " >&2; fi -done -echo " ⏳ Burst отправка..." -wait -echo "" - -BURST_OK=0 -for b in $(seq 1 $BURST_SIZE); do - [[ -f "${TMPDIR_STRESS}/burst_${b}.result" ]] && BURST_OK=$((BURST_OK + $(cat "${TMPDIR_STRESS}/burst_${b}.result"))) -done -SEC5_DUR=$(($(date +%s) - SEC5_START)) -echo " Результат: ${BURST_OK}/${BURST_SIZE} за ${SEC5_DUR}s" -check_ge "Burst >= 90%" "$BURST_OK" "$((BURST_SIZE * 90 / 100))" - -sleep 3 -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}" -check_eq "Burst все в очереди" "${BURST_APPROX:-0}" "$BURST_OK" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 6. LONG-POLLING + параллельные producer'ы -# ═══════════════════════════════════════════════════════════ -echo "═══ 6. Long-polling: consumer WaitTime=10s + 5 producers ═══" -SEC6_START=$(date +%s) - -LONGPOLL_Q="stress-longpoll-${TS}" -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$LONGPOLL_Q" \ - --attributes '{"ReceiveMessageWaitTimeSeconds":"10"}') -LONGPOLL_QURL=$(qurl "$T1_ID" "$LONGPOLL_Q") - -# Consumer: читает с long-poll, считает сколько получит за 30s -( - end=$((SECONDS + 30)) - total_recv=0 - while [[ $SECONDS -lt $end ]]; do - R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$LONGPOLL_QURL" \ - --max-number-of-messages 10 --wait-time-seconds 10 2>&1) - cnt=$(echo "$R" | grep -c '"MessageId"' || true) - total_recv=$((total_recv + cnt)) - if (( total_recv % 10 == 0 && total_recv > 0 )); then printf "[LP-recv:${total_recv}] " >&2; fi - done - echo "$total_recv" > "${TMPDIR_STRESS}/longpoll_recv.result" -) & - -# Producers: начинают через 3s, каждый отправляет 20 msg с паузами -sleep 3 -for p in $(seq 1 5); do - ( - ok=0 - for m in $(seq 1 20); do - R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$LONGPOLL_QURL" \ - --message-body "longpoll-p${p}-m${m}" 2>&1) - if echo "$R" | grep -q "MessageId"; then ok=$((ok + 1)); fi - sleep 0.2 - done - echo "$ok" > "${TMPDIR_STRESS}/longpoll_prod_${p}.result" - printf "[LP-prod${p}:${ok}] " >&2 - ) & -done -echo " ⏳ Long-poll: consumer ждёт, producers отправляют (30s)..." -wait -echo "" - -LP_SENT=0 -for p in $(seq 1 5); do - [[ -f "${TMPDIR_STRESS}/longpoll_prod_${p}.result" ]] && LP_SENT=$((LP_SENT + $(cat "${TMPDIR_STRESS}/longpoll_prod_${p}.result"))) -done -LP_RECV=$(cat "${TMPDIR_STRESS}/longpoll_recv.result" 2>/dev/null || echo 0) -SEC6_DUR=$(($(date +%s) - SEC6_START)) -echo " Producers отправили: $LP_SENT, consumer получил: $LP_RECV за ${SEC6_DUR}s" -check_ge "Long-poll: получено >= 50% отправленных" "$LP_RECV" "$((LP_SENT / 2))" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 7. ДВОЙНОЕ УДАЛЕНИЕ — race на receipt handle -# ═══════════════════════════════════════════════════════════ -echo "═══ 7. Двойное удаление: 2 воркера гонятся за одно сообщение ═══" - -RACE_Q="stress-race-${TS}" -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$RACE_Q") -RACE_QURL=$(qurl "$T1_ID" "$RACE_Q") - -# Отправляем 20 сообщений -for m in $(seq 1 20); do - sqs "$T1_AK" "$T1_SK" send-message --queue-url "$RACE_QURL" \ - --message-body "race-${m}" >/dev/null 2>&1 -done -echo " Отправлено 20 сообщений" - -# VisibilityTimeout=0 — чтобы оба воркера получали одни сообщения -RACE_DOUBLE_DEL=0 -RACE_OK_DEL=0 -for round in $(seq 1 10); do - # Два воркера одновременно получают - R1=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$RACE_QURL" \ - --max-number-of-messages 1 --visibility-timeout 30 2>&1) - HANDLE=$(echo "$R1" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+' | head -1) - if [[ -z "$HANDLE" ]]; then continue; fi - - # Два параллельных delete - D1=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$RACE_QURL" --receipt-handle "$HANDLE" 2>&1) & - PID1=$! - D2=$(sqs "$T1_AK" "$T1_SK" delete-message --queue-url "$RACE_QURL" --receipt-handle "$HANDLE" 2>&1) & - PID2=$! - wait $PID1 $PID2 - RACE_OK_DEL=$((RACE_OK_DEL + 1)) - printf "[race-${round}] " >&2 -done -echo "" -echo " Раундов race: ${RACE_OK_DEL}/10" -# Главное: сервер НЕ упал -R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes --queue-url "$RACE_QURL" \ - --attribute-names ApproximateNumberOfMessages 2>&1) -check "Сервер жив после double-delete" "$R" "ApproximateNumberOfMessages" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 8. BATCH-ОПЕРАЦИИ конкурентно -# ═══════════════════════════════════════════════════════════ -echo "═══ 8. Batch-операции: ${BATCH_WORKERS} воркеров SendMessageBatch + DeleteMessageBatch ═══" -SEC8_START=$(date +%s) - -BATCH_Q="stress-batch-${TS}" -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$BATCH_Q") -BATCH_QURL=$(qurl "$T1_ID" "$BATCH_Q") - -# Параллельные SendMessageBatch (10 msg per batch × 10 workers = 100 msg) -for w in $(seq 1 $BATCH_WORKERS); do - ( - # Формируем batch из 10 записей - ENTRIES="" - for e in $(seq 1 10); do - [[ -n "$ENTRIES" ]] && ENTRIES="${ENTRIES}," - ENTRIES="${ENTRIES}{\"Id\":\"${w}-${e}\",\"MessageBody\":\"batch-w${w}-e${e}\"}" - done - R=$(sqs "$T1_AK" "$T1_SK" send-message-batch --queue-url "$BATCH_QURL" \ - --entries "[${ENTRIES}]" 2>&1) - ok=$(echo "$R" | grep -c '"MessageId"' || true) - echo "$ok" > "${TMPDIR_STRESS}/batch_send_${w}.result" - printf "[B-send${w}:${ok}] " >&2 - ) & -done -echo " ⏳ SendMessageBatch..." -wait -echo "" - -BATCH_SENT=0 -for w in $(seq 1 $BATCH_WORKERS); do - [[ -f "${TMPDIR_STRESS}/batch_send_${w}.result" ]] && BATCH_SENT=$((BATCH_SENT + $(cat "${TMPDIR_STRESS}/batch_send_${w}.result"))) -done -echo " Batch отправлено: ${BATCH_SENT}" -check_ge "Batch: отправлено >= 80" "$BATCH_SENT" "80" - -# Параллельные ReceiveMessage + DeleteMessageBatch -BATCH_DELETED=0 -for w in $(seq 1 5); do - ( - del=0; empty=0 - while [[ $empty -lt 3 ]]; do - R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$BATCH_QURL" \ - --max-number-of-messages 10 --wait-time-seconds 1 2>&1) - HANDLES=$(echo "$R" | grep -oP '"ReceiptHandle"\s*:\s*"\K[^"]+') - if [[ -z "$HANDLES" ]]; then empty=$((empty + 1)); continue; fi - empty=0 - # Формируем batch delete - ENTRIES="" - idx=0 - while IFS= read -r handle; do - [[ -z "$handle" ]] && continue - [[ -n "$ENTRIES" ]] && ENTRIES="${ENTRIES}," - ENTRIES="${ENTRIES}{\"Id\":\"${idx}\",\"ReceiptHandle\":\"${handle}\"}" - idx=$((idx + 1)) - done <<< "$HANDLES" - if [[ -n "$ENTRIES" ]]; then - DR=$(sqs "$T1_AK" "$T1_SK" delete-message-batch --queue-url "$BATCH_QURL" \ - --entries "[${ENTRIES}]" 2>&1) - ok_del=$(echo "$DR" | grep -c '"Id"' || true) - del=$((del + ok_del)) - fi - done - echo "$del" > "${TMPDIR_STRESS}/batch_del_${w}.result" - printf "[B-del${w}:${del}] " >&2 - ) & -done -echo " ⏳ DeleteMessageBatch..." -wait -echo "" - -BATCH_DEL_TOTAL=0 -for w in $(seq 1 5); do - [[ -f "${TMPDIR_STRESS}/batch_del_${w}.result" ]] && BATCH_DEL_TOTAL=$((BATCH_DEL_TOTAL + $(cat "${TMPDIR_STRESS}/batch_del_${w}.result"))) -done -SEC8_DUR=$(($(date +%s) - SEC8_START)) -echo " Batch удалено: $BATCH_DEL_TOTAL за ${SEC8_DUR}s" -check_ge "Batch: удалено >= 80%" "$BATCH_DEL_TOTAL" "$((BATCH_SENT * 80 / 100))" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 9. МНОГО ОЧЕРЕДЕЙ — 30 очередей на тенанта -# ═══════════════════════════════════════════════════════════ -echo "═══ 9. Queue-flood: ${QUEUE_FLOOD_COUNT} очередей, send+receive в каждую ═══" -SEC9_START=$(date +%s) - -# Создаём 30 очередей параллельно -for q in $(seq 1 $QUEUE_FLOOD_COUNT); do - ( - qname="stress-flood-${q}-${TS}" - my_qurl=$(qurl "$T1_ID" "$qname") - # Создаём - R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$qname" 2>&1) - if ! echo "$R" | grep -q "QueueUrl"; then echo "0" > "${TMPDIR_STRESS}/flood_${q}.result"; exit 0; fi - # Отправляем 5 msg - for m in $(seq 1 5); do - sqs "$T1_AK" "$T1_SK" send-message --queue-url "$my_qurl" \ - --message-body "flood-q${q}-m${m}" >/dev/null 2>&1 - done - # Читаем 1 - R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$my_qurl" \ - --max-number-of-messages 1 --wait-time-seconds 1 2>&1) - if echo "$R" | grep -q "MessageId"; then echo "1" > "${TMPDIR_STRESS}/flood_${q}.result" - else echo "0" > "${TMPDIR_STRESS}/flood_${q}.result"; fi - if (( q % 10 == 0 )); then printf "[Q${q}] " >&2; fi - ) & -done -echo " ⏳ Создание и тест ${QUEUE_FLOOD_COUNT} очередей..." -wait -echo "" - -FLOOD_OK=0 -for q in $(seq 1 $QUEUE_FLOOD_COUNT); do - [[ -f "${TMPDIR_STRESS}/flood_${q}.result" ]] && FLOOD_OK=$((FLOOD_OK + $(cat "${TMPDIR_STRESS}/flood_${q}.result"))) -done -SEC9_DUR=$(($(date +%s) - SEC9_START)) -echo " Успешных: ${FLOOD_OK}/${QUEUE_FLOOD_COUNT} за ${SEC9_DUR}s" -check_ge "Queue-flood: >= 90% очередей работают" "$FLOOD_OK" "$((QUEUE_FLOOD_COUNT * 90 / 100))" - -# Проверяем ListQueues — должно быть >= 30 flood-очередей -R=$(sqs "$T1_AK" "$T1_SK" list-queues --queue-name-prefix "stress-flood" 2>&1) -LISTED=$(echo "$R" | grep -c "stress-flood" || true) -echo " ListQueues видит: $LISTED очередей" -check_ge "ListQueues >= ${QUEUE_FLOOD_COUNT}" "$LISTED" "$QUEUE_FLOOD_COUNT" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 10. MEMORY CHECK — RSS до нагрузки -# ═══════════════════════════════════════════════════════════ -echo "═══ 10. Memory check — запоминаем RSS ═══" -RSS_BEFORE=$(get_pod_rss_mb) -echo " RSS до тяжёлой фазы: ${RSS_BEFORE} MB" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 11. KILL POD + восстановление из Redis -# ═══════════════════════════════════════════════════════════ -echo "═══ 11. Kill pod → восстановление из Redis ═══" - -# Перед kill — отправляем в burst-очередь ещё 20 msg -EXTRA=20 -for m in $(seq 1 $EXTRA); do - sqs "$T1_AK" "$T1_SK" send-message --queue-url "$BURST_QURL" \ - --message-body "pre-kill-${m}-${TS}" >/dev/null 2>&1 - if (( m % 5 == 0 )); then printf "[pre-kill:${m}] " >&2; fi -done -echo "" - -R=$(sqs "$T1_AK" "$T1_SK" get-queue-attributes --queue-url "$BURST_QURL" \ - --attribute-names ApproximateNumberOfMessages 2>&1) -BEFORE_KILL=$(echo "$R" | grep -oP '"ApproximateNumberOfMessages"\s*:\s*"\K[0-9]+') -echo " Сообщений в burst до kill: ${BEFORE_KILL:-?}" - -kill_pod -echo " Ждём рестарт..." -wait_for_pod_ready -wait_for_healthy - -sleep 3 -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}" -check_eq "Данные восстановлены из Redis" "${AFTER_KILL:-0}" "${BEFORE_KILL}" - -R=$(admin_api GET "/admin/tenants") -TC=$(echo "$R" | grep -c "stress-t1-${TS}" || true) -check_ge "Тенанты восстановлены" "$TC" "1" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 12. REDIS DISCONNECT / RECONNECT -# ═══════════════════════════════════════════════════════════ -echo "═══ 12. Redis disconnect simulation ═══" - -cat <<'NETPOL' | kubectl apply -n "$NAMESPACE" -f - 2>&1 | head -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 applied — Redis заблокирован" -sleep 5 - -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 при потере Redis: $R_CODE" -TOTAL=$((TOTAL + 1)) -if [[ "$R_CODE" =~ ^[2345][0-9][0-9]$ ]]; then - PASS=$((PASS + 1)); echo " ✅ Сервис отвечает (HTTP $R_CODE)" -else - FAIL=$((FAIL + 1)); echo " ❌ Сервис не отвечает (HTTP $R_CODE)" -fi - -kubectl delete networkpolicy stress-test-block-redis -n "$NAMESPACE" 2>&1 | head -1 -echo " NetworkPolicy удалена — Redis доступен" -sleep 5 - -R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$BURST_QURL" \ - --message-body "after-redis-recovery-${TS}" 2>&1) -check "Сервис работает после Redis reconnect" "$R" "MessageId" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 13. СМЕШАННАЯ НАГРУЗКА — send + receive + delete + getattr (20s) -# ═══════════════════════════════════════════════════════════ -echo "═══ 13. Смешанная нагрузка: 10 senders + 10 receivers + 3 getattr × ${MIXED_DURATION}s ═══" -SEC13_START=$(date +%s) - -MIXED_Q="stress-mixed-${TS}" -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$MIXED_Q") -MIXED_QURL=$(qurl "$T1_ID" "$MIXED_Q") - -# Предзагрузка 100 msg -for m in $(seq 1 100); do - sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MIXED_QURL" \ - --message-body "mixed-init-${m}" >/dev/null 2>&1 - if (( m % 20 == 0 )); then printf "[init:${m}] " >&2; fi -done -echo "" -echo " Начальная загрузка: 100 msg" - -# 10 Отправителей -for w in $(seq 1 10); do - ( - end=$((SECONDS + MIXED_DURATION)); ok=0 - while [[ $SECONDS -lt $end ]]; do - R=$(sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MIXED_QURL" \ - --message-body "mixed-s${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 - -# 10 Читателей-удалителей -for w in $(seq 1 10); do - ( - end=$((SECONDS + MIXED_DURATION)); recv=0 - while [[ $SECONDS -lt $end ]]; do - 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 "$T1_AK" "$T1_SK" delete-message --queue-url "$MIXED_QURL" \ - --receipt-handle "$handle" >/dev/null 2>&1 - recv=$((recv + 1)) - done <<< "$HANDLES" - done - echo "$recv" > "${TMPDIR_STRESS}/mixed_recv_${w}.result" - ) & -done - -# 3 GetQueueAttributes -for w in $(seq 1 3); do - ( - end=$((SECONDS + MIXED_DURATION)); ok=0; fail=0 - while [[ $SECONDS -lt $end ]]; do - 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)); else fail=$((fail + 1)); fi - sleep 0.5 - done - echo "${ok}:${fail}" > "${TMPDIR_STRESS}/mixed_attr_${w}.result" - ) & -done - -# Прогресс каждые 5 сек -( - elapsed=0 - while [[ $elapsed -lt $MIXED_DURATION ]]; do - sleep 5 - elapsed=$((elapsed + 5)) - printf "[mixed: ${elapsed}/${MIXED_DURATION}s] " >&2 - done -) & -PROGRESS_PID=$! - -echo " ⏳ Смешанная нагрузка ${MIXED_DURATION}s..." -wait -echo "" - -MIXED_SENT=0; MIXED_RECV=0; ATTR_OK=0; ATTR_FAIL=0 -for w in $(seq 1 10); 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 3); 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 -SEC13_DUR=$(($(date +%s) - SEC13_START)) -echo " Отправлено: $MIXED_SENT, прочитано+удалено: $MIXED_RECV | Attr: ok=$ATTR_OK fail=$ATTR_FAIL | ${SEC13_DUR}s" -check_ge "Mixed: отправлено > 0" "$MIXED_SENT" "1" -check_ge "Mixed: прочитано > 0" "$MIXED_RECV" "1" -check_eq "Mixed: GetQueueAttributes 0 сбоев" "$ATTR_FAIL" "0" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 14. MEMORY CHECK — после нагрузки -# ═══════════════════════════════════════════════════════════ -echo "═══ 14. Memory check — RSS после нагрузки ═══" -RSS_AFTER=$(get_pod_rss_mb) -echo " RSS до: ${RSS_BEFORE} MB, после: ${RSS_AFTER} MB" -if [[ "$RSS_BEFORE" -gt 0 && "$RSS_AFTER" -gt 0 ]]; then - RSS_GROWTH=$((RSS_AFTER - RSS_BEFORE)) - echo " Рост: ${RSS_GROWTH} MB" - # Допускаем рост до 100 MB — выше уже подозрительно - check_le "Рост RSS <= 100MB" "$RSS_GROWTH" "100" -else - echo " ⚠️ Не удалось измерить RSS (возможно /proc недоступен)" - TOTAL=$((TOTAL + 1)); PASS=$((PASS + 1)) - echo " ✅ Memory check пропущен (нет доступа к /proc)" -fi -echo "" - -# ═══════════════════════════════════════════════════════════ -# 15. MULTI-KILL: kill → up → kill → up -# ═══════════════════════════════════════════════════════════ -echo "═══ 15. Multi-kill: 3 рестарта подряд ═══" -SEC15_START=$(date +%s) - -# Отправляем маркерное сообщение -MARKER_Q="stress-marker-${TS}" -R=$(sqs "$T1_AK" "$T1_SK" create-queue --queue-name "$MARKER_Q") -MARKER_QURL=$(qurl "$T1_ID" "$MARKER_Q") -sqs "$T1_AK" "$T1_SK" send-message --queue-url "$MARKER_QURL" \ - --message-body "marker-survive-3-kills-${TS}" >/dev/null 2>&1 - -for k in 1 2 3; do - echo " --- Kill #${k} ---" - kill_pod - wait_for_pod_ready - wait_for_healthy - echo " Kill #${k}: под восстановлен ✓" -done - -# Маркер выжил? -R=$(sqs "$T1_AK" "$T1_SK" receive-message --queue-url "$MARKER_QURL" \ - --max-number-of-messages 1 --wait-time-seconds 2 2>&1) -SEC15_DUR=$(($(date +%s) - SEC15_START)) -echo " 3 рестарта за ${SEC15_DUR}s" -check "Маркер выжил 3 рестарта" "$R" "marker-survive-3-kills" -echo "" - -# ═══════════════════════════════════════════════════════════ -# 16. CLEANUP -# ═══════════════════════════════════════════════════════════ -echo "═══ 16. Cleanup ═══" -echo " Удаляю очереди..." - -# Основные очереди -for i in $(seq 1 $TENANT_COUNT); do - tak=$(cat "${TMPDIR_STRESS}/tak_${i}" 2>/dev/null) - tsk=$(cat "${TMPDIR_STRESS}/tsk_${i}" 2>/dev/null) - tid=$(cat "${TMPDIR_STRESS}/tid_${i}" 2>/dev/null) - [[ -n "$tak" ]] && sqs "$tak" "$tsk" delete-queue --queue-url "${BASE_URL}/${tid}/${STRESS_Q}" >/dev/null 2>&1 -done -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 -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$LONGPOLL_QURL" >/dev/null 2>&1 -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$RACE_QURL" >/dev/null 2>&1 -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$BATCH_QURL" >/dev/null 2>&1 -sqs "$T1_AK" "$T1_SK" delete-queue --queue-url "$MARKER_QURL" >/dev/null 2>&1 - -# Flood-очереди -for q in $(seq 1 $QUEUE_FLOOD_COUNT); do - sqs "$T1_AK" "$T1_SK" delete-queue \ - --queue-url "$(qurl "$T1_ID" "stress-flood-${q}-${TS}")" >/dev/null 2>&1 & -done -wait - -echo " Очереди удалены" - -TOTAL_DUR=$(($(date +%s) - STARTED_AT)) -echo "" -echo "╔═══════════════════════════════════════════════════════════════════╗" -echo "║ ИТОГО: ${PASS}/${TOTAL} ✅ ${FAIL}/${TOTAL} ❌ ║" -echo "║ Время: ${TOTAL_DUR}s ║" -echo "╚═══════════════════════════════════════════════════════════════════╝" - -if [[ $FAIL -gt 0 ]]; then exit 1; fi