Files
autotest/DOCS/sonnet-question-tracker.md

156 lines
8.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ⚠️ LEGACY — НЕАКТУАЛЬНО. Исторический документ.
# Вопрос к Sonnet — архитектура трекера инстансов + UI кнопок
Отправлено: 27.07.2026
---
Ты — senior архитектор. Разбери проблему в Flask-приложении на Nubes pythonk8s (gunicorn, 1 worker).
## Контекст
Приложение app-autotest — веб-интерфейс для автотестов операций облачных сервисов Nubes.
Репозиторий: https://gitea.services.ngcloud.ru/forcloud/app-autotest.git
Версия: v1.0.47
## Файлы для изучения
| Файл | Назначение |
|------|-----------|
| `site/app.py` | Точка входа Flask, VERSION, blueprints |
| `site/routes/api_test.py` | POST /api/test (запуск), GET /api/test/status (поллинг), _finish_op (фоновый поток) |
| `site/routes/main.py` | Главная страница, /api/operations/<svcId> (список инстансов из трекера) |
| `site/operations/tracker.py` | Запись/чтение /tmp/instances.json (_INITIAL, add, remove, list_all) |
| `site/templates/index.html` | UI: executeOp(), showStages(), поллинг, refreshInstances() |
| `site/api/http_client.py` | HTTP-клиент (Bearer auth, User-Agent) |
Полные пути в репозитории:
```
app-autotest/site/app.py
app-autotest/site/routes/api_test.py
app-autotest/site/routes/main.py
app-autotest/site/operations/tracker.py
app-autotest/site/templates/index.html
app-autotest/site/api/http_client.py
```
## Поток CREATE (как работает сейчас)
1. UI: пользователь жмёт «Создать» → showParams(18, 'create') → заполняет форму → executeOp(params)
2. UI отправляет POST /api/test `{serviceId:1, operation:"create", svcOperationId:18, displayName, params, instanceUid:""}`
3. Бэкенд api_test():
- `client.post("/instances", {serviceId, displayName})` → получает instanceUid
- `client.post("/instanceOperations", {instanceUid, operation:"create"})` → получает opUid
- `client.post("/instanceOperationCfsParams", ...)` — для каждого параметра
- `client.post("/instanceOperations/{opUid}/run")` — запуск
- `threading.Thread(target=_finish_op, args=(client, opUid, instanceUid, ...), daemon=True).start()`
- возвращает `{status:"RUNNING", opUid, instanceUid}`
4. UI начинает поллинг: `GET /api/test/status/<opUid>` каждые 2 секунды
5. _finish_op (фоновый daemon-поток):
- поллит `GET /instanceOperations/{opUid}?fields=dtFinish,isSuccessful,errorLog,isInProgress,duration,stages`
- обновляет `_op_results[opUid]` (in-memory dict)
- когда `dtFinish != null` и `isSuccessful == true`:
- **вызывает tracker_add(instanceUid, svcId, displayName)** ← ПРОБЛЕМА ЗДЕСЬ
- устанавливает `_op_results[opUid] = {status:"OK", stages, duration}`
- таймаут 300 секунд
6. UI при status=="OK": вызывает `selectService(SVC_ID)``GET /api/operations/1` → читает tracker → показывает список
## ПРОБЛЕМА
Инстанс создаётся в Nubes (виден в UI платформы), НО в списке инстансов приложения НЕ появляется.
`tracker_add` не вызывается → `/tmp/instances.json` не обновляется → инстанс не в списке.
**Происходило 2 раза подряд** (v1.0.46 и v1.0.47). Оба раза инстанс в Nubes есть, в трекере — нет.
## Моя гипотеза
tracker_add вызывается внутри daemon-потока _finish_op. В gunicorn:
- Воркер может быть перезапущен платформой в любой момент
- Daemon-потоки умирают вместе с воркером — молча, без логов
- Python не пишет traceback при смерти daemon-потока
## Текущий код _finish_op (api_test.py, строки 153-195)
```python
def _finish_op(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, is_create, is_delete=False):
"""Фоном ждать dtFinish и сохранить результат."""
import time
t0 = time.time()
deadline = t0 + 300
while time.time() < deadline:
try:
data = client.get(f"/instanceOperations/{op_uid}?fields=dtFinish,isSuccessful,errorLog,isInProgress,duration,stages")
except Exception:
time.sleep(5)
continue
op = data.get("instanceOperation", {})
dt_finish = op.get("dtFinish")
_op_results[op_uid] = {
"status": "RUNNING",
"stages": op.get("stages", []),
"duration": round(time.time() - t0, 1),
}
if dt_finish and str(dt_finish).strip():
is_ok = op.get("isSuccessful")
err = op.get("errorLog") or ""
# Сначала трекер — чтобы UI при poll уже видел инстанс
if is_ok:
if is_create:
tracker_add(instance_uid, svc_id, display_name)
elif is_delete:
tracker_remove(instance_uid)
_op_results[op_uid] = {
"status": "OK" if is_ok else "FAIL",
"error": str(err) if err else "",
"stages": op.get("stages", []),
"duration": round(time.time() - t0, 1),
}
return
time.sleep(5)
_op_results[op_uid] = {"status": "TIMEOUT", "duration": round(time.time() - t0, 1)}
```
## Вопросы
### 1. Где вызывать tracker_add?
Вариант А: синхронно в api_test(), сразу после получения instanceUid от API (шаг 3), до запуска потока.
- Плюс: гарантированная запись, инстанс в трекере мгновенно
- Минус: если операция потом упадёт — в трекере «мусорный» инстанс (но это лучше чем отсутствие)
Вариант Б: subprocess.Popen вместо threading.Thread.
- Плюс: процесс живёт независимо от gunicorn worker
- Минус: сложнее, нужен IPC для _op_results
Вариант В: sqlite3 с WAL-режимом.
- Плюс: атомарная запись, конкурентный доступ
- Минус: без persistent volume данные теряются при редеплое (как и /tmp/)
### 2. Что делать с _op_results?
Сейчас это module-level dict. При нескольких gunicorn workers — каждый worker имеет свой dict. Нужен ли переход на sqlite/file-based storage для _op_results?
### 3. Надёжность под gunicorn
Как правильно организовать фоновую работу под gunicorn на pythonk8s (1 worker, нет PV, нет Redis/RabbitMQ)?
## Дополнительно: UI кнопок
Нужен CSS чтобы кнопки операций шли горизонтально в 1-2 ряда, как в Nubes UI:
```
delete | modify | suspend | resume | redeploy | reconcile
```
Сейчас они в vertical списке внутри `div.inst-ops`. Предложи стиль.
## Ограничения платформы
- `site/` — НЕ пакет (без __init__.py, конфликт с stdlib site.py)
- Импорты без префикса site.: `from api.http_client import ...`
- `app.run(host="0.0.0.0", port=5000)` — обязательно
- Gunicorn запускается платформой, предположительно 1 worker
- Нет persistent volume — данные в /tmp/ теряются при редеплое
- Деплой: git push → managed service подхватывает → редеплой