fix: semaphore+singleflight for ensureUserNS, fix 504 on 10 parallel new users (v0.8.12)
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# fission-console v0.6.9 — Отчёт о тестировании
|
||||
|
||||
**Дата:** 2026-04-19
|
||||
**Версия:** `naeel/fission-console:v0.6.9`
|
||||
**Ветка:** `feat/namespace-isolation`
|
||||
|
||||
---
|
||||
|
||||
## Что реализовано
|
||||
|
||||
Managed serverless functions service поверх Fission + Kubernetes. Go-бэкенд (консоль), REST API, k8s dynamic client. Пользователи изолированы по namespace (`fission-{SHA256(sub)[:16]}`).
|
||||
|
||||
**Поддерживаемые среды выполнения:**
|
||||
- Node.js 22 (ESM, `module.exports` / `export default` / анонимная функция)
|
||||
- Python 3.11 (Flask, `def main():` без аргументов)
|
||||
|
||||
**Auth:** JWT или TEST_MODE (`X-Test-Sub: user@domain` → SHA256 → namespace)
|
||||
|
||||
---
|
||||
|
||||
## Баги исправлены в этой сессии (v0.6.8 → v0.6.9)
|
||||
|
||||
| # | Баг | Симптом | Фикс |
|
||||
|---|-----|---------|------|
|
||||
| 1 | Route collision | Два разных пользователя создавали функцию с одинаковым маршрутом | Префикс последних 12 символов namespace: `/{ns[-12:]}/{fn-name}` |
|
||||
| 2 | INVOKE несуществующей → 200 | Вызов несуществующей функции возвращал HTTP 200 без ошибки | GET из k8s перед invoke, `IsNotFound` → 404 |
|
||||
| 3 | DELETE несуществующей → 200 | Удаление несуществующей функции возвращало `deleted:true` | Инвертирован `if err == nil` → `if err != nil`, 404 |
|
||||
| 4 | Orphan package при невалидном TTL | `parseTTL` вызывался после создания пакета — при ошибке оставался мусорный package в k8s | `parseTTL` перемещён **до** создания k8s-ресурсов |
|
||||
|
||||
---
|
||||
|
||||
## Результаты тестирования
|
||||
|
||||
```
|
||||
PASS=41 FAIL=0
|
||||
```
|
||||
|
||||
**Покрытие (16 блоков):**
|
||||
|
||||
| Блок | Что проверяется |
|
||||
|------|----------------|
|
||||
| A | Регресс v0.6.8: INVOKE/DELETE несуществующей → 404, route isolation |
|
||||
| B | Валидация входных данных → 400 (missing name/code/language, пробелы) |
|
||||
| C | Дублирующее создание → конфликт |
|
||||
| D | UPDATE кода + повторный invoke возвращает новый код |
|
||||
| E | GET функции (200/404) |
|
||||
| F | LIST изоляция — пользователь видит только свои функции |
|
||||
| G | Namespace изоляция DELETE — нельзя удалить чужую функцию |
|
||||
| H | Python `def main():` (без аргументов) → invoke 200 |
|
||||
| I | Node.js: `module.exports`, `export.handler`, `export default` |
|
||||
| J | TTL: expires_at создаётся / не создаётся / невалидный → 400 |
|
||||
| K | `ctx.request.url` доступен внутри функции |
|
||||
| L | Stress: 10 параллельных invoke → 10/10 OK |
|
||||
| M | LIST пустого namespace → `[]` |
|
||||
| N | DELETE полная цепочка: Function + Package + HTTPTrigger удаляются |
|
||||
| O | Запрос без auth-заголовка → 401 |
|
||||
| P | POST /auth: без токена → 400, невалидный токен → 401 |
|
||||
|
||||
---
|
||||
|
||||
## Архитектурные решения — вопросы для ревью
|
||||
|
||||
### 1. Namespace on-demand
|
||||
Namespace создаётся при первом обращении пользователя. Нет отдельного registration flow.
|
||||
**Вопрос:** правильно ли это? Нет ли рисков при параллельном первом запросе от одного пользователя (race condition на создание namespace)?
|
||||
|
||||
### 2. Сборка функций
|
||||
Код пользователя пакуется в zip в памяти и передаётся в Fission Package как `literal` (base64). При обновлении создаётся новый Package, старый удаляется.
|
||||
**Вопрос:** нет версионирования. Стоит ли хранить историю версий?
|
||||
|
||||
### 3. Reaper
|
||||
Горутина раз в 30 секунд проверяет `expires-at` аннотацию и удаляет протухшие функции вместе с Package и HTTPTrigger. Работает без персистентного стейта — при рестарте пода начинает заново со следующего цикла.
|
||||
**Вопрос:** надёжно ли это? Что если под упал в момент удаления — останется ли мусор?
|
||||
|
||||
### 4. Python env и сигнатура main()
|
||||
Официальный `ghcr.io/fission/python-env` вызывает `main(*args)` через Flask. Реально аргументы не передаются (только через route params). При `def main():` работает. При `def main(ctx):` — `TypeError: main() missing 1 required positional argument`.
|
||||
**Вопрос:** патчить env или задокументировать ограничение?
|
||||
|
||||
### 5. Валидация имени функции
|
||||
Имя с пробелами возвращает 502 (k8s отклоняет по RFC 1123), а не 400 (явная валидация в API).
|
||||
**Вопрос:** стоит ли добавить regex-валидацию имени на уровне API до обращения в k8s?
|
||||
|
||||
---
|
||||
|
||||
## Что ещё не сделано
|
||||
|
||||
- [ ] Версионирование функций
|
||||
- [ ] Явная валидация имени функции (RFC 1123) в API
|
||||
- [ ] Метрики / биллинг
|
||||
- [ ] Документация API (OpenAPI spec)
|
||||
- [ ] Merge в master
|
||||
Reference in New Issue
Block a user