From a76baa62a3f98afc433c4215ccb1e9df7711ea09 Mon Sep 17 00:00:00 2001 From: Naeel Date: Sun, 22 Mar 2026 08:11:05 +0300 Subject: [PATCH] =?UTF-8?q?fix:=20v0.1.50=20=E2=80=94=20runtime=20400=20+?= =?UTF-8?q?=20SLESS=5FENTRYPOINT=20=D0=B2=20Deployment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bug 1: services.go — k8s IsInvalid error (CRD enum validation) маппился в 500. Теперь errors.IsInvalid() → 400 Bad Request (invalid service spec). Bug 2: service_controller.go buildServiceDeployment не передавал env SLESS_ENTRYPOINT в под. Добавлен в envVars из svc.Spec.Entrypoint. Без него server.py использовал fallback handler.handle и не замечал неверный entrypoint. operator_failure_test.sh 12B-2: обновлён под новое правильное поведение — create ruby3.0 → 400 (не 201). Старый 201-путь сохранён как warn для совместимости. --- controllers/service_controller.go | 6 ++++ doc/decisions/log.md | 19 ++++++++++ doc/progress.md | 58 ++++++++++++++++++++++++++++++- examples/POSTGRES/main.tf | 2 ++ internal/api/handler/services.go | 6 ++++ operator_failure_test.sh | 16 +++++---- 6 files changed, 100 insertions(+), 7 deletions(-) diff --git a/controllers/service_controller.go b/controllers/service_controller.go index 899974f..a45d93b 100644 --- a/controllers/service_controller.go +++ b/controllers/service_controller.go @@ -317,6 +317,12 @@ func (r *ServiceReconciler) buildServiceDeployment(svc *slessv1alpha1.Service, n for _, k := range keys { envVars = append(envVars, corev1.EnvVar{Name: k, Value: svc.Spec.Env[k]}) } + // SLESS_ENTRYPOINT обязан быть передан в pod — runtime server использует его для загрузки функции. + // Если не передать, server.py/nodejs/go server упадёт на fallback handler.handle и не заметит + // неверный entrypoint, что маскирует проблему конфигурации. + if svc.Spec.Entrypoint != "" { + envVars = append(envVars, corev1.EnvVar{Name: "SLESS_ENTRYPOINT", Value: svc.Spec.Entrypoint}) + } return &appsv1.Deployment{ ObjectMeta: metav1.ObjectMeta{ diff --git a/doc/decisions/log.md b/doc/decisions/log.md index b034c0d..08e9eb2 100644 --- a/doc/decisions/log.md +++ b/doc/decisions/log.md @@ -2,6 +2,25 @@ --- +## 2026-03-21 — Оценка трудозатрат на проект + +| Компонент | Оценка | +|-----------|--------| +| Go operator — CRD (Function, Trigger, Job), 3 контроллера, reconcile loops, self-healing | 80-100 ч | +| REST API — router, middleware, 8 handlers, namespace lifecycle | 40-50 ч | +| Terraform провайдер — provider, client, 4 ресурса | 40-60 ч | +| Builder — kaniko, S3 upload, context tar | 20-30 ч | +| Runtimes — Go/Node/Python базовые образы | 20-30 ч | +| Инфраструктура — k8s manifests, kustomize, Harbor, Postgres | 20-30 ч | +| Тесты — lifecycle (47) + survival (34), ~1900 строк bash | 40-60 ч | +| Документация — architecture, decisions, errors, API, handoffs | 20-30 ч | + +**Итого: ~280-390 человеко-часов** (7-10 недель одного разработчика в нормальном темпе). + +С AI-ассистентом в паре реальное живое время ~80-120 часов (30-50% от полного объёма). + +--- + ## 2026-03-21 — timeout_sec для sless_service: 0 = нет лимита (не 30s по умолчанию) ### Контекст diff --git a/doc/progress.md b/doc/progress.md index e36d556..19509cd 100644 --- a/doc/progress.md +++ b/doc/progress.md @@ -1,6 +1,62 @@ # Прогресс разработки -Последнее обновление: 2026-03-21 (operator v0.1.49 — оба бага исправлены; lifecycle-тест 47/47 PASS) +Последнее обновление: 2026-03-21 + +--- + +## TODO (backlog — не скоро) + +| # | Задача | Заметка | +|---|--------|---------| +| T1 | `nubes_endpoint` в sless провайдере — сделать обязательным или ввести флаг `require_token_validation` | Сейчас если `nubes_endpoint` не задан — проверка токена через nubes API пропускается. Упущение безопасности. | +| T2 | **Terraform провайдер nubes — end-to-end тестирование** | Провайдер написан но ни разу не прогонялся с реальным сервером. Нужно: `init → apply → apply (idempotency) → update → destroy`. Тест-кейсы уже есть в `examples/`. Это отдельный большой блок работы для облачных DevOps-инженеров nubes. | + +--- + +## TODO (завтра — приоритет) + +### ПЛАН: multi-user тест с Postgres + +**Шаг 1 — Добавить Postgres в survival тест (один юзер):** +- Функции в `operator_survival_test.sh` сейчас echo-хендлеры без PG +- Добавить группу тестов где функция делает реальные INSERT/SELECT в Postgres +- Postgres креды берутся из параметров ресурса `sless_pg` (через `env_vars` функции) +- Изоляция: таблица называется `test_` — каждый юзер пишет в свою +- Прогнать от одного юзера → убедиться что работает + +**Шаг 2 — Параметризовать survival тест:** +- `TOKEN` и `NAMESPACE` через env переменные (сейчас хардкодные) +- `PG_DSN` через env переменную (берётся из параметров Postgres ресурса) + +**Шаг 3 — Обёртка на 10 юзеров:** +- `operator_multiuser_test.sh` +- Генерирует 10 фейковых JWT (`test-user-01` ... `test-user-10`) +- Для каждого вычисляет namespace (SHA256(sub)[:8 байт]) +- `POST /v1/namespaces/{ns}/ensure` — создаёт namespace +- Параллельно запускает полный survival тест в каждом namespace (`&`) +- Ждёт всех (`wait`), собирает итоговый отчёт: PASS/FAIL по каждому юзеру + +**Ожидаемый результат:** ~340 тестов (34×10), ~15-20 минут параллельно + +--- + +## 2026-03-21 — Сессия 7: survival-тест (группы 5-10) + +### Что сделано + +| # | Компонент | Результат | +|---|-----------|-----------| +| 1 | `operator_survival_test.sh` — написан (928 строк, 6 групп) | ✅ запущен | +| 2 | Группа 5: FLOOD BUILD — 6 функций (3×Python + 3×Node.js), 9 тестов | 🔄 в процессе | +| 3 | Группа 6: RAPID DISCARD STORM — 30 create→DELETE, 4 теста | 🔄 в процессе | +| 4 | Группа 7: CHURN UNDER FIRE — 10 PUT под 200 invokes, 9 тестов | 🔄 в процессе | +| 5 | Группа 8: PHOENIX — 3 цикла DELETE+recreate | 🔄 в процессе | +| 6 | Группа 9: SELF-HEALING MARATHON — 5×kill Deployment | 🔄 в процессе | +| 7 | Группа 10: INVOKE HURRICANE — 500 parallel invokes | 🔄 в процессе | + +Лог: VM `/tmp/survival.log`, ожидаемое время: ~75-100 мин + +--- --- diff --git a/examples/POSTGRES/main.tf b/examples/POSTGRES/main.tf index d47cd26..26cb6d7 100644 --- a/examples/POSTGRES/main.tf +++ b/examples/POSTGRES/main.tf @@ -49,6 +49,7 @@ variable "pg_password" { # API Dashboard (для Terraform-провайдеров): https://deck-api-test.ngcloud.ru/api/v1/index.cfm # UI облака (только браузер, не для кода): https://deck-test.ngcloud.ru/ # ВАЖНО: nubes и sless провайдеры требуют API endpoint, НЕ UI! + provider "nubes" { api_token = var.api_token api_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm" @@ -60,3 +61,4 @@ provider "sless" { nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1" } + diff --git a/internal/api/handler/services.go b/internal/api/handler/services.go index 1f6a44f..f9e422e 100644 --- a/internal/api/handler/services.go +++ b/internal/api/handler/services.go @@ -157,6 +157,12 @@ func (h *Handler) CreateService(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusConflict, errResp("service already exists")) return } + // k8s возвращает StatusInvalid (422) при нарушении enum-валидации CRD (например, неизвестный runtime) + // — маппим это в 400, а не в 500 + if errors.IsInvalid(err) { + writeJSON(w, http.StatusBadRequest, errResp("invalid service spec: "+err.Error())) + return + } writeJSON(w, http.StatusInternalServerError, errResp(err.Error())) return } diff --git a/operator_failure_test.sh b/operator_failure_test.sh index 230e6cc..cc4a177 100644 --- a/operator_failure_test.sh +++ b/operator_failure_test.sh @@ -415,17 +415,21 @@ else fail "create $CORRUPT_FN → HTTP $create_code" fi -# 12B-2: Неверный runtime ("ruby3.0") — создаётся, но upload падает +# 12B-2: Неверный runtime ("ruby3.0") — CRD enum validation отклоняет немедленно +# v0.1.50: исправлено — services.go теперь маппит k8s IsInvalid → 400 вместо 500 BADRT_FN="badrt-${SFX}" CLEANUP_NAMES+=("$BADRT_FN") -info "12B-2 runtime='ruby3.0' → create ожидаем 201, upload ожидаем 400..." +info "12B-2 runtime='ruby3.0' → create ожидаем 400 (CRD enum validation)..." create_code2=$(api_code POST "$API/services" \ "{\"name\":\"$BADRT_FN\",\"runtime\":\"ruby3.0\",\ \"entrypoint\":\"handler.handle\",\"memory_mb\":128}") -if [[ "$create_code2" == "201" ]]; then - pass "runtime=ruby3.0 → create HTTP 201 (API не валидирует runtime имена)" +if [[ "$create_code2" == "400" ]]; then + pass "runtime=ruby3.0 → create HTTP 400 (CRD enum validation отклонил немедленно)" + CLEANUP_NAMES=( "${CLEANUP_NAMES[@]/$BADRT_FN}" ) +elif [[ "$create_code2" == "201" ]]; then + # Старое поведение (до v0.1.50): API принимал любое имя runtime, ошибка на upload + warn "runtime=ruby3.0 → create HTTP 201 (CRD enum validation не работает — проверь CRD)" note "GAP: API принимает любое runtime имя при создании; ошибка только на upload." - # Загружаем минимальный zip — PrepareContext упадёт на runtimeBaseImage("ruby3.0") make_minimal_py_zip "$BADRT_FN" uc2=$(upload_zip "$BADRT_FN" "/tmp/minimal_${BADRT_FN}.zip") if [[ "$uc2" == "400" ]]; then @@ -437,7 +441,7 @@ if [[ "$create_code2" == "201" ]]; then "$(api_code DELETE "$API/services/$BADRT_FN")" "204" CLEANUP_NAMES=( "${CLEANUP_NAMES[@]/$BADRT_FN}" ) else - fail "runtime=ruby3.0 → create HTTP $create_code2 (ожидали 201)" + fail "runtime=ruby3.0 → create HTTP $create_code2 (ожидали 400)" fi # 12B-3: Пустой zip (без handler.py) — upload и build проходят, но контейнер падает