diff --git a/HISTORY/2026-08-14-session-log.md b/HISTORY/2026-08-14-session-log.md index e0194c0..ed44a40 100644 --- a/HISTORY/2026-08-14-session-log.md +++ b/HISTORY/2026-08-14-session-log.md @@ -519,3 +519,30 @@ DeleteQueue всегда успешен; NextSequenceNumber пересоздаё **Проверки**: go build OK (v0.1.35), go vet чистый (кроме известной некомпиляции тестов gosqs — пакет fixtures), go test app/models OK. +**Ход деплоя и эмпирические уточнения**: +- Первый вариант фикса resolveProtocol («query-mode → XML») оказался НЕВЕРНЫМ: + эмпирика показала, что AWS CLI v2 в query-mode парсит именно JSON-ответы + (list-queues на XML вернул None, на JSON — список). Откачено: протокол ответа + по Content-Type, как было; дефект «Code=None» закрыт правильным форматом + JSON-ошибок {"__type":"com.amazonaws.sqs#Code","message":"..."} в encodeResponse + и GeneralError в actionHandler. +- Whitelist имён атрибутов для JSON-протокола реализован через + QueueAttributes.UnmarshalJSON (единый список models.AttrNameWhitelist); + в whitelist включены FifoQueue/ContentBasedDeduplication (иначе CLI v2 + не мог создать FIFO — регрессия была поймана api_test). +- Прод v0.1.35, digest `sha256:7d1c82e9…` (по digest). + +**Проверки против прода (v0.1.35)**: +- Пробы ошибок: пустое тело → MissingParameter; max=11 → InvalidParameterValue; + VisibilityTimeout=43201 → InvalidParameterValue; FooBar=1 → InvalidParameterValue; + повторный purge → PurgeQueueInProgress; delete несуществующей → NonExistentQueue; + FIFO без MessageGroupId → MissingParameter. Все с AWS-кодами (Code=None устранён). +- api_test.sh (CLI v2): 22/22 PASS. sdk_test.py (boto3): 15/15 PASS. +- Целостность 25 циклов: с ВМ 25/25 PASS (lost=0, mismatch=0, errors=0). + С локальной машины периодически ReceiptHandleIsInvalid — анализ логов пода + показал: это retry-дубли (первый delete успешен, ответ теряется на пути + клиент→шлюз; сообщение фактически удалено и подбирается следующими циклами). + Инфраструктурная потеря ответов (DDoS-Guard/Nubes gateway), не дефект сервиса. +- smoke.sh: 11/11 PASS. +- 30-мин нагрузочный тест с ВМ — в процессе. + diff --git a/app/admin/admin.go b/app/admin/admin.go index 372c157..b35e8ee 100644 --- a/app/admin/admin.go +++ b/app/admin/admin.go @@ -245,7 +245,7 @@ func (h *Handler) jwtAuth(w http.ResponseWriter, r *http.Request) { // Auto-provisioning: тенант и ключи детерминированы из email. // Токен — только аутентификация; ключи в ответ не возвращаем (GET /ui/api/credentials). - t, err := h.store.CreateFromJWT(claims.Sub, claims.Email, 10) + t, err := h.store.CreateFromJWT(claims.Sub, claims.Email, tenant.DefaultTenantMaxQueues) if err != nil { log.Errorf("jwt auth: failed to create tenant: %v", err) jsonErr(w, http.StatusInternalServerError, "failed to provision tenant") diff --git a/app/tenant/tenant_store.go b/app/tenant/tenant_store.go index 9fdd05d..bf14ec4 100644 --- a/app/tenant/tenant_store.go +++ b/app/tenant/tenant_store.go @@ -18,6 +18,11 @@ import ( // MaxTenantsGlobal — глобальный лимит тенантов (защита от OOM, Fix #12) const MaxTenantsGlobal = 1000 +// DefaultTenantMaxQueues — лимит очередей для JWT-тенантов (auto-provisioning). +// Используется в app/admin при логине; при каждом логине лимит существующего +// тенанта обновляется до этого значения (см. CreateFromJWT). +const DefaultTenantMaxQueues = 50 + // Tenant — модель тенанта shared-sqs. // AccessKey используется как идентификатор в AWS Authorization header. type Tenant struct { @@ -215,6 +220,16 @@ func (s *TenantStore) CreateFromJWT(sub, email string, maxQueues int) (*Tenant, if sub != "" { existing.NubesSub = sub } + // Обновляем лимит очередей на актуальное значение из кода при каждом + // логине и персистим в Redis — иначе после рестарта пода из Redis + // восстановится старый лимит (было: тенант навсегда оставался с лимитом, + // заданным при первом логине). + if existing.MaxQueues != maxQueues { + existing.MaxQueues = maxQueues + if data, err := json.Marshal(existing); err == nil { + persistence.SaveTenantRaw(existing.ID, data) + } + } return existing, nil } // Легаси: убираем старую запись из всех индексов и из Redis, ниже создаём новую