Рефакторинг API-слоя, операций и БД
This commit is contained in:
+70
-12
@@ -1,45 +1,103 @@
|
||||
"""
|
||||
Общие утилиты: извлечение UUID из ответов API Nubes.
|
||||
|
||||
Используется: api_test.py, scenario.py, executor.py.
|
||||
Заменяет разрозненные реализации _find_uid / _uid_from_location.
|
||||
Проблема: Nubes API возвращает UUID инстанса/операции в разных форматах
|
||||
в зависимости от эндпоинта и фазы жизненного цикла:
|
||||
- POST /instances → {"instanceUid": "..."} (прямой ключ)
|
||||
- POST /instances → Location: "./uuid" (заголовок)
|
||||
- POST /instanceOperations → {"instanceOperationUid": "..."} (другой ключ)
|
||||
- GET /instanceOperations → {"instanceOperation": {"instanceOperationUid": "..."}} (вложенный)
|
||||
|
||||
Эти две функции — единый способ достать UUID из ЛЮБОГО ответа.
|
||||
Раньше каждая точка вызова имела свою реализацию _find_uid / _uid_from_location.
|
||||
Теперь всё здесь — один источник правды.
|
||||
|
||||
Используется: executor.py, api_test.py, scenario.py.
|
||||
"""
|
||||
|
||||
|
||||
def find_uid(resp):
|
||||
"""Извлечь instanceUid или instanceOperationUid из ответа API.
|
||||
"""Извлечь instanceUid или instanceOperationUid из dict-ответа API.
|
||||
|
||||
Ищет в нескольких местах:
|
||||
1. На верхнем уровне: instanceOperationUid → instanceUid → uid → Uid
|
||||
2. Во вложенных dict-значениях: {key: {instanceOperationUid: ..., instanceUid: ...}}
|
||||
Стратегия поиска (в порядке приоритета):
|
||||
1. Верхний уровень — прямые ключи (стиль api_test.py):
|
||||
instanceOperationUid → instanceUid → uid → Uid
|
||||
2. Вложенные dict-значения (стиль scenario.py):
|
||||
Для каждого значения-словаря проверяем instanceOperationUid → instanceUid
|
||||
|
||||
Почему такой порядок:
|
||||
- instanceOperationUid приоритетнее — это ответ на POST /instanceOperations
|
||||
- instanceUid — ответ на POST /instances или GET /instances
|
||||
- uid / Uid — legacy-форматы, почти не встречаются
|
||||
|
||||
Args:
|
||||
resp: dict — распарсенный JSON-ответ API (или что угодно).
|
||||
|
||||
Returns:
|
||||
str или None — UUID (36 символов) или None если не нашли.
|
||||
"""
|
||||
# Защита: если передали не dict (например, list или str) — сразу None
|
||||
if not isinstance(resp, dict):
|
||||
return None
|
||||
# Верхний уровень — прямые ключи (api_test.py-стиль)
|
||||
|
||||
# ── Уровень 1: прямые ключи на верхнем уровне ──
|
||||
# Пример: {"instanceUid": "abc-123", ...}
|
||||
# Проверяем по порядку: сначала специфичные, потом общие
|
||||
for key in ("instanceOperationUid", "instanceUid", "uid", "Uid"):
|
||||
v = resp.get(key)
|
||||
# isinstance(str) — отсекаем числа, None, пустые строки
|
||||
if isinstance(v, str) and v:
|
||||
return v
|
||||
# Вложенные dict-значения (scenario.py-стиль)
|
||||
|
||||
# ── Уровень 2: вложенные dict-значения ──
|
||||
# Пример: {"instanceOperation": {"instanceOperationUid": "abc-123"}}
|
||||
# Перебираем ВСЕ значения resp, ищем вложенные словари
|
||||
for v in resp.values():
|
||||
if isinstance(v, dict):
|
||||
# Внутри вложенного dict — те же ключи что на верхнем уровне
|
||||
uid = v.get("instanceOperationUid") or v.get("instanceUid")
|
||||
if uid:
|
||||
return uid
|
||||
|
||||
# Ничего не нашли
|
||||
return None
|
||||
|
||||
|
||||
def uid_from_location(loc):
|
||||
"""Извлечь UUID из Location-заголовка.
|
||||
"""Извлечь UUID из Location-заголовка HTTP-ответа.
|
||||
|
||||
Location бывает: "./UUID", "/api/v1/svc/instances/UUID", "/api/v1/svc/instanceOperations/UUID"
|
||||
Берём последний сегмент после split("/").
|
||||
Location — это HTTP-заголовок, который Nubes API возвращает при создании
|
||||
ресурса (201 Created). Форматы:
|
||||
- "./uuid" (относительный)
|
||||
- "/api/v1/svc/instances/uuid" (абсолютный путь)
|
||||
- "/api/v1/svc/instanceOperations/uuid" (полный путь)
|
||||
|
||||
Алгоритм:
|
||||
1. Убираем trailing slash.
|
||||
2. Split по "/" — берём ПОСЛЕДНИЙ сегмент.
|
||||
3. Проверяем что это похоже на UUID (>= 32 символа) и не ".".
|
||||
|
||||
Args:
|
||||
loc: str или None — значение заголовка Location.
|
||||
|
||||
Returns:
|
||||
str или None — UUID или None если не нашли.
|
||||
"""
|
||||
# Пустой Location → нечего извлекать
|
||||
if not loc:
|
||||
return None
|
||||
|
||||
# Убираем trailing slash (напр. "./uuid/" → "./uuid")
|
||||
# Split по "/" → [".", "uuid"] или ["", "api", "v1", "svc", "instances", "uuid"]
|
||||
parts = str(loc).rstrip("/").split("/")
|
||||
|
||||
# Последний сегмент — это UUID
|
||||
uid = parts[-1]
|
||||
# UUID — 36 символов с 4 дефисами (уже проверено в http_client.py)
|
||||
|
||||
# Проверки:
|
||||
# uid != "." — Location "./" даст пустой последний сегмент
|
||||
# len >= 32 — UUID всегда 36 символов (8-4-4-4-12), но на всякий случай >= 32
|
||||
if uid and uid != "." and len(uid) >= 32:
|
||||
return uid
|
||||
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user