Files
app-autotest/site/api/http_client.py
T

130 lines
5.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
HTTP-клиент для Nubes API + автоопределение стенда.
Класс HttpClient — тонкая обёртка над requests.Session:
- Добавляет заголовки: Authorization Bearer, User-Agent (DDoS-Guard)
- GET: raise_for_status → .json()
- POST: проверка r.ok, извлечение Location-заголовка
Функции автостенда:
- detect_endpoint(token) — пробует dev→test стенды, возвращает URL
- stand_name(endpoint) — "dev" / "test" по URL
- create_client(token, fallback) — HttpClient + endpoint (одним вызовом)
"""
import requests
# Список стендов для автоопределения. Порядок важен: dev первый (быстрее).
STANDS = [
"https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc",
"https://lk-api-gateway-test.ngcloud.ru/api/v1/svc",
]
def detect_endpoint(token):
"""Пробуем токен против dev и test стендов, возвращаем рабочий URL.
Делает GET /instances?pageSize=1 на каждый стенд.
Если results != None — стенд рабочий, возвращаем его URL.
Если ни один не подошёл — возвращаем None."""
for ep in STANDS:
try:
c = HttpClient(ep, token)
data = c.get("/instances", params={"pageSize": 1, "page": 1})
if data.get("results") is not None:
return ep
except Exception:
continue
return None
def stand_name(endpoint):
"""dev/test по URL стенда.
Ищет подстроку 'dev' или 'test' в URL.
Если не найдено — возвращает '?'."""
for name in ("dev", "test"):
if name in (endpoint or ""):
return name
return "?"
def create_client(token, fallback_endpoint=None):
"""HttpClient с автоопределением стенда по токену.
Сначала detect_endpoint(token), если не сработало — fallback_endpoint.
Возвращает кортеж (HttpClient, endpoint_url) или None если стенд не определён."""
ep = detect_endpoint(token) or fallback_endpoint
if not ep:
return None
return HttpClient(ep, token), ep
class HttpClient:
"""HTTP-клиент для Nubes REST API.
Использует requests.Session для keep-alive соединений.
Добавляет обязательные заголовки:
- Authorization: Bearer <token> (аутентификация)
- User-Agent: Mozilla/5.0 (DDoS-Guard требует НЕ python-requests)"""
def __init__(self, endpoint, token):
# Убираем trailing slash чтобы потом добавлять "/path"
self._endpoint = endpoint.rstrip("/")
# Session — переиспользует TCP-соединения между запросами
self._session = requests.Session()
self._session.headers.update({
"Authorization": f"Bearer {token}",
"User-Agent": "Mozilla/5.0",
})
def get(self, path, **kwargs):
"""GET-запрос. Таймаут по умолчанию 10 секунд.
Вызывает raise_for_status() — при 4xx/5xx выбрасывает HTTPError.
Возвращает распарсенный JSON (dict/list)."""
kwargs.setdefault("timeout", 10)
r = self._session.get(f"{self._endpoint}{path}", **kwargs)
r.raise_for_status()
if not r.text or not r.text.strip():
return {} # пустой ответ (напр. validate-cfs успех)
return r.json()
def post(self, path, data=None, **kwargs):
"""POST-запрос. Таймаут по умолчанию 30 секунд.
Отправляет data как JSON (json=...).
При HTTP-ошибке выбрасывает Exception с кодом и телом ответа.
Возвращает dict:
- Распарсенный JSON (если ответ — валидный JSON-объект)
- + ключ "_location" со значением заголовка Location (если есть)
Location нужен для получения UID созданного ресурса (instanceUid, opUid)."""
kwargs.setdefault("timeout", 30)
url = f"{self._endpoint}{path}"
# json=... — requests сам сериализует и ставит Content-Type: application/json
r = self._session.post(url, json=(data if data is not None else {}), **kwargs)
if not r.ok:
raise Exception(f"POST {path}: {r.status_code} {r.reason}: {r.text[:200]}")
result = {}
try:
parsed = r.json()
if isinstance(parsed, dict):
result = parsed
except Exception:
pass # тело не JSON или не dict — ок, Location всё равно извлечём
loc = r.headers.get("Location", "")
if loc:
# Location бывает вида "./UUID" или "/api/v1/svc/instanceOperations/UUID"
result["_location"] = loc
return result
def raw_delete(self, url):
"""DELETE-запрос к произвольному URL (CMDB API).
Не добавляет Nubes-эндпоинт — используется для жёсткого удаления инстансов.
Возвращает True если 2xx, иначе False."""
try:
r = self._session.delete(url, timeout=10)
return r.ok
except Exception:
return False