v1.0.51: auto-detect stand (dev/test); init instance_groups; _finish_op full try/except

This commit is contained in:
2026-07-27 09:54:49 +04:00
parent a271e863f3
commit 1786bc67dd
4 changed files with 203 additions and 47 deletions
+131
View File
@@ -0,0 +1,131 @@
# Пошаговый аудит кода — 27.07.2026 (v1.0.50)
## Методология
Полная трассировка CREATE-потока: UI → api_test() → _finish_op() → api_test_status() → refreshInstances() → api_operations(). Каждый if, try, except, присваивание.
---
## Результат: найдено 3 бага
### 🔴 Баг #1 (КРИТИЧЕСКИЙ): Multi-worker gunicorn — in-memory dict не работает
**Файл:** `site/operations/tracker.py`
```python
_data = {
"408b7f96-...": {...}, # 4 initial items
}
def add(instance_uid, svc_id, display_name):
with _LOCK:
_data[instance_uid] = {...}
```
`_data` — module-level dict. У каждого gunicorn worker своя копия модуля → свой `_data`.
**Трассировка:**
1. POST /api/test → попадает на **воркер A**
2. `tracker_add(...)``_data` воркера A = 5 элементов ✅
3. GET /api/operations/1 → попадает на **воркер B**
4. `tracker_list()``_data` воркера B = 4 элемента ❌
**Почему это объясняет ВСЕ симптомы:**
- После F5 — рандомный воркер → опять 4
- v1.0.46-1.0.49 — ни одно решение не помогало (in-memory принципиально не跨-process)
- Инстанс в Nubes есть, в трекере нет — разные воркеры
**Решение:** вернуть файловый трекер (`/tmp/instances.json`) с межпроцессной блокировкой (`fcntl.flock` вместо `threading.Lock`).
---
### 🔴 Баг #2 (КРИТИЧЕСКИЙ): _finish_op() умирает молча на не-словаре
**Файл:** `site/routes/api_test.py`, строки 176-179
```python
def _finish_op(...):
while time.time() < deadline:
try:
data = client.get(...) # ← except ловит ТОЛЬКО это
except Exception:
time.sleep(5)
continue
op = data.get("instanceOperation", {}) # ← если data не dict → AttributeError!
```
Если Nubes API возвращает `list`, `str`, `None` или любой не-словарь — `data.get()`**AttributeError**. Этот except НЕ покрывает строку 179. Поток умирает молча. Python daemon-потоки не пишут traceback.
**Решение:** обернуть ВСЁ тело цикла (строки 166-195) в `try/except Exception: print(traceback)`.
---
### 🟡 Баг #3 (НЕКРИТИЧНЫЙ): _op_results — та же multi-worker проблема
**Файл:** `site/routes/api_test.py`, строка 152
```python
_op_results = {} # module-level
# В _finish_op (воркер A):
_op_results[op_uid] = {"status": "OK", ...}
# В api_test_status (воркер B):
if op_uid in _op_results: # ← False! (другой воркер)
return jsonify(_op_results[op_uid])
# fallback → прямой запрос в Nubes API
```
**Некритично** потому что есть fallback: если `_op_results` не содержит op_uid, `api_test_status()` делает прямой GET в Nubes API и возвращает статус. UI получает stages напрямую из Nubes, не из `_op_results`.
**НО:** fallback не обновляет `tracker_remove` для delete. При multi-worker delete не удалит инстанс из трекера.
---
## Полная трассировка CREATE (все шаги)
| Шаг | Код | Результат | Статус |
|-----|-----|-----------|--------|
| 1 | `data = request.get_json()` | `svc_id=1, op_name="create", display_name="autotest-1-xxx"` | ✅ |
| 2 | `client.post("/instances", ...)` | `instance_uid = "f192b10d-..."` | ✅ |
| 3 | `client.post("/instanceOperations", ...)` | `op_uid = "d489348e-..."` | ✅ |
| 4 | `for pid,pval: client.post("/instanceOperationCfsParams", ...)` | Параметры установлены | ✅ |
| 5 | `client.post("/instanceOperations/{op_uid}/run")` | Операция запущена | ✅ |
| 6 | `tracker_add(instance_uid, svc_id, display_name)` | `_data[uid] = {...}` (in-memory) | ✅ |
| 7 | `threading.Thread(target=_finish_op, ...)` | Поток запущен | ✅ |
| 8 | `return {status:"RUNNING", opUid, instanceUid}` | Ответ UI | ✅ |
| 9 | UI poll `/api/test/status/<opUid>` | `_op_results[opUid]` или fallback API | ⚠️ разн. воркеры |
| 10 | `_finish_op` детектит `dtFinish` | `_op_results[opUid] = {status:"OK"}` | ⚠️ воркер A |
| 11 | UI: `refreshInstances()``/api/operations/1` | `tracker_list()` → 4 элемента | ❌ воркер B |
| 12 | `api_operations()` возвращает 4 инстанса | Нового нет | ❌ |
| 13 | F5 → `selectService(1)``/api/operations/1` | Опять 4 | ❌ |
---
## Дополнительные находки (не критические)
### ⚠️ params loop — orphaned resources при ошибке
Если `client.post("/instanceOperationCfsParams", ...)` падает на mid-param:
- Инстанс УЖЕ создан в Nubes (сирота)
- Операция УЖЕ создана (сирота)
- `tracker_add` НЕ вызван (он после цикла)
- Ответ: FAIL
### ⚠️ `data["svcOperationId"]` — KeyError если поле отсутствует
Вызов API без `svcOperationId` в JSON → KeyError → FAIL. Обработано внешним try/except.
### ⚠️ `int(pid)` — ValueError если param ID не число
`int("abc")` → ValueError → FAIL. Обработано внешним try/except.
### ⚠️ `_find_uid()` итерация по ВСЕМ значениям
Может случайно найти uid во вложенном объекте. Низкий риск, но нечисто.
---
## План исправлений для v1.0.51
| # | Что | Как |
|---|-----|-----|
| 1 | Файловый трекер с fcntl.flock | Вернуть `/tmp/instances.json`, заменить `threading.Lock` на `fcntl.flock` |
| 2 | _finish_op: try/except на всё тело | Обернуть строки 166-195 в `try/except: print(traceback)` |
| 3 | (опционально) `svc_id = int(data["serviceId"])` | Защита от строкового "1" в JSON |