fix: v0.1.50 — runtime 400 + SLESS_ENTRYPOINT в Deployment

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 для совместимости.
This commit is contained in:
Naeel
2026-03-22 08:11:05 +03:00
parent d4ccbc7d15
commit a76baa62a3
6 changed files with 100 additions and 7 deletions
+6
View File
@@ -317,6 +317,12 @@ func (r *ServiceReconciler) buildServiceDeployment(svc *slessv1alpha1.Service, n
for _, k := range keys { for _, k := range keys {
envVars = append(envVars, corev1.EnvVar{Name: k, Value: svc.Spec.Env[k]}) 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{ return &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{ ObjectMeta: metav1.ObjectMeta{
+19
View File
@@ -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 по умолчанию) ## 2026-03-21 — timeout_sec для sless_service: 0 = нет лимита (не 30s по умолчанию)
### Контекст ### Контекст
+57 -1
View File
@@ -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_<namespace>` — каждый юзер пишет в свою
- Прогнать от одного юзера → убедиться что работает
**Шаг 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 мин
---
--- ---
+2
View File
@@ -49,6 +49,7 @@ variable "pg_password" {
# API Dashboard (для Terraform-провайдеров): https://deck-api-test.ngcloud.ru/api/v1/index.cfm # API Dashboard (для Terraform-провайдеров): https://deck-api-test.ngcloud.ru/api/v1/index.cfm
# UI облака (только браузер, не для кода): https://deck-test.ngcloud.ru/ # UI облака (только браузер, не для кода): https://deck-test.ngcloud.ru/
# ВАЖНО: nubes и sless провайдеры требуют API endpoint, НЕ UI! # ВАЖНО: nubes и sless провайдеры требуют API endpoint, НЕ UI!
provider "nubes" { provider "nubes" {
api_token = var.api_token api_token = var.api_token
api_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm" 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" nubes_endpoint = "https://deck-api-test.ngcloud.ru/api/v1"
} }
+6
View File
@@ -157,6 +157,12 @@ func (h *Handler) CreateService(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusConflict, errResp("service already exists")) writeJSON(w, http.StatusConflict, errResp("service already exists"))
return 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())) writeJSON(w, http.StatusInternalServerError, errResp(err.Error()))
return return
} }
+10 -6
View File
@@ -415,17 +415,21 @@ else
fail "create $CORRUPT_FN → HTTP $create_code" fail "create $CORRUPT_FN → HTTP $create_code"
fi 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}" BADRT_FN="badrt-${SFX}"
CLEANUP_NAMES+=("$BADRT_FN") 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" \ create_code2=$(api_code POST "$API/services" \
"{\"name\":\"$BADRT_FN\",\"runtime\":\"ruby3.0\",\ "{\"name\":\"$BADRT_FN\",\"runtime\":\"ruby3.0\",\
\"entrypoint\":\"handler.handle\",\"memory_mb\":128}") \"entrypoint\":\"handler.handle\",\"memory_mb\":128}")
if [[ "$create_code2" == "201" ]]; then if [[ "$create_code2" == "400" ]]; then
pass "runtime=ruby3.0 → create HTTP 201 (API не валидирует runtime имена)" 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." note "GAP: API принимает любое runtime имя при создании; ошибка только на upload."
# Загружаем минимальный zip — PrepareContext упадёт на runtimeBaseImage("ruby3.0")
make_minimal_py_zip "$BADRT_FN" make_minimal_py_zip "$BADRT_FN"
uc2=$(upload_zip "$BADRT_FN" "/tmp/minimal_${BADRT_FN}.zip") uc2=$(upload_zip "$BADRT_FN" "/tmp/minimal_${BADRT_FN}.zip")
if [[ "$uc2" == "400" ]]; then if [[ "$uc2" == "400" ]]; then
@@ -437,7 +441,7 @@ if [[ "$create_code2" == "201" ]]; then
"$(api_code DELETE "$API/services/$BADRT_FN")" "204" "$(api_code DELETE "$API/services/$BADRT_FN")" "204"
CLEANUP_NAMES=( "${CLEANUP_NAMES[@]/$BADRT_FN}" ) CLEANUP_NAMES=( "${CLEANUP_NAMES[@]/$BADRT_FN}" )
else else
fail "runtime=ruby3.0 → create HTTP $create_code2 (ожидали 201)" fail "runtime=ruby3.0 → create HTTP $create_code2 (ожидали 400)"
fi fi
# 12B-3: Пустой zip (без handler.py) — upload и build проходят, но контейнер падает # 12B-3: Пустой zip (без handler.py) — upload и build проходят, но контейнер падает