# ⚠️ 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` ```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/` | `_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 |