Files
app-autotest/site/api/utils.py

104 lines
4.9 KiB
Python

"""
Общие утилиты: извлечение UUID из ответов API Nubes.
Проблема: 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 из dict-ответа API.
Стратегия поиска (в порядке приоритета):
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
# ── Уровень 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
# ── Уровень 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-заголовка HTTP-ответа.
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]
# Проверки:
# uid != "." — Location "./" даст пустой последний сегмент
# len >= 32 — UUID всегда 36 символов (8-4-4-4-12), но на всякий случай >= 32
if uid and uid != "." and len(uid) >= 32:
return uid
return None