Files
autotest/DOCS/step-by-step-audit-v1.0.50.md
T

6.4 KiB
Raw Blame History

⚠️ 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.

Трассировка:

  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

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