6.4 KiB
⚠️ LEGACY — НЕАКТУАЛЬНО. Исторический документ.
Пошаговый аудит кода — 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
_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.
Трассировка:
- POST /api/test → попадает на воркер A
tracker_add(...)→_dataворкера A = 5 элементов ✅- GET /api/operations/1 → попадает на воркер B
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
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
_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 |