Клиент дописан до всех двадцати семи эндпоинтов платформы. Добавлено: - регионы, образы с разбивкой на системы и готовые сборки, вторая версия API образов - все действия над сервером: старт, стоп, перезагрузка, переустановка, смена тарифа, клонирование, откат, консоль, бэкапы - снимки целиком, включая ожидание готовности и поиск поднятых из них серверов - ключи доступа, IP-адреса с обратными записями, приватные сети, kubeconfig - история операций, биллинг, расход в час и в сутки - сборка и проверка slug тарифа по правилам платформы - ошибки разложены по смыслу, чтобы ловить прицельно Claude-Session: https://claude.ai/code/session_01Loz5stgQZH6ofBdMqTxiJu
135 строки
6.5 KiB
Python
135 строки
6.5 KiB
Python
"""Справочники платформы и сборка slug'ов.
|
|
|
|
Часть этих данных API не отдаёт вовсе, часть отдаёт неполно, поэтому держим
|
|
их тут. Проверено 23.08.2026, при расхождении верить панели управления,
|
|
раздел настроек, пункт со slug'ами для API и Terraform.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
# Регионы. Список подтверждён самой платформой в тексте ошибки валидации.
|
|
REGIONS = {
|
|
"openstack-msk1": "Москва",
|
|
"openstack-msk2": "Москва-2",
|
|
"openstack-msk3": "Москва-3",
|
|
"openstack-spb1": "Санкт-Петербург",
|
|
"openstack-sam1": "Самара",
|
|
"openstack-fz1": "Москва ФЗ-152",
|
|
"msk1": "Москва, архивные тарифы",
|
|
}
|
|
|
|
# Линейки обычных тарифов
|
|
PLAN_LINES = {
|
|
"std": "Стандартный",
|
|
"stdp": "Стандартный плюс",
|
|
"hp": "Производительный",
|
|
"hps": "Производительный ФЗ-152",
|
|
"ddc": "Выделенный CPU",
|
|
"high": "Высокочастотный",
|
|
}
|
|
|
|
# Допустимые сочетания ядер и памяти по линейкам
|
|
CPU_RAM = {
|
|
"std": {1: [1, 2, 3, 4], 2: [2, 3, 6, 8], 3: [3, 4, 6, 8, 10],
|
|
4: [4, 6, 8, 10, 12, 16], 6: [6, 8, 10, 12, 16],
|
|
8: [8, 10, 12, 16, 20, 24, 32], 10: [10, 12, 16, 20, 24, 32, 40],
|
|
12: [12, 16, 20, 24, 32, 40, 48], 16: [16, 20, 24, 32, 40, 48],
|
|
20: [20, 24, 32, 40, 48], 24: [24, 32, 40, 48]},
|
|
"hp": {1: [1, 2, 3, 4], 2: [2, 3, 6, 8], 3: [3, 4, 6, 8, 10],
|
|
4: [4, 6, 8, 10, 12, 16], 6: [6, 8, 10, 12, 16],
|
|
8: [8, 10, 12, 16, 20, 24, 32], 10: [10, 12, 16, 20, 24, 32, 40],
|
|
12: [12, 16, 20, 24, 32, 40, 48], 16: [16, 20, 24, 32, 40, 48],
|
|
20: [20, 24, 32, 40, 48, 56, 64], 24: [24, 32, 40, 48, 56, 64, 72],
|
|
32: [32, 40, 48, 56, 64, 72, 80], 40: [40, 48, 56, 64, 72, 80],
|
|
48: [48, 56, 64, 72, 80]},
|
|
"high": {1: [1, 2, 3, 4], 2: [2, 3, 6, 8], 3: [3, 4, 6, 8, 10],
|
|
4: [4, 6, 8, 10, 12, 16], 6: [6, 8, 10, 12, 16],
|
|
8: [8, 10, 12, 16, 20, 24, 32], 10: [10, 12, 16, 20, 24, 32],
|
|
12: [12, 16, 20, 24, 32]},
|
|
}
|
|
CPU_RAM["stdp"] = CPU_RAM["std"]
|
|
CPU_RAM["hps"] = CPU_RAM["hp"]
|
|
CPU_RAM["ddc"] = CPU_RAM["hp"]
|
|
|
|
# Диапазон диска по линейкам, гигабайты, шаг десять
|
|
DISK_RANGE = {
|
|
"std": (10, 480), "stdp": (10, 480),
|
|
"hp": (10, 960), "hps": (10, 960), "ddc": (10, 960),
|
|
"high": (20, 320),
|
|
}
|
|
|
|
# Тарифы с видеокартой. Свои конфигурации для них собирать нельзя,
|
|
# только эти готовые. Цены в рублях за час на 23.08.2026.
|
|
GPU_SIZES = {
|
|
"c8-m32-d128-g1-a4000-gpu": ("A4000 16 ГБ", 8, 32, 128, 37.80),
|
|
"c16-m64-d256-g1-a4000-gpu": ("A4000 16 ГБ", 16, 64, 256, 51.87),
|
|
"c16-m64-d256-g1-a5000-gpu": ("A5000 24 ГБ", 16, 64, 256, 57.00),
|
|
"c24-m128-d512-g1-a5000-gpu": ("A5000 24 ГБ", 24, 128, 512, 82.79),
|
|
"c24-m128-d512-g2-a5000-gpu": ("2x A5000 24 ГБ", 24, 128, 512, 111.65),
|
|
"c32-m256-d1024-g1-a5000-gpu": ("A5000 24 ГБ", 32, 256, 1024, 132.01),
|
|
"c48-m384-d1024-g2-a5000-gpu": ("2x A5000 24 ГБ", 48, 384, 1024, 203.30),
|
|
"c16-m128-d512-g1-a100-gpu": ("A100 80 ГБ", 16, 128, 512, 222.14),
|
|
"c32-m256-d1024-g1-a100-gpu": ("A100 80 ГБ", 32, 256, 1024, 273.72),
|
|
"c64-m512-d1024-g2-a100-gpu": ("2x A100 80 ГБ", 64, 512, 1024, 529.14),
|
|
"c24-m96-d384-g1-rtx6000-gpu": ("RTX 6000 Blackwell", 24, 96, 384, 205.07),
|
|
"c32-m128-d512-g1-rtx6000-gpu": ("RTX 6000 Blackwell", 32, 128, 512, 223.43),
|
|
"c64-m192-d1024-g1-rtx6000-gpu": ("RTX 6000 Blackwell", 64, 192, 1024, 271.15),
|
|
"c32-m128-d1024-g2-rtx6000-gpu": ("2x RTX 6000 Blackwell", 32, 128, 1024, 382.58),
|
|
}
|
|
|
|
# Типы образов в API
|
|
IMAGE_TYPES = ("distribution", "application", "snapshot", "backup", "restore")
|
|
|
|
# Действия над сервером
|
|
ACTIONS = ("start", "stop", "reboot", "rebuild", "password_reset", "resize",
|
|
"generate_vnc_link", "snapshot", "enable_backups", "disable_backups",
|
|
"restore", "clone", "resize_isp_license")
|
|
|
|
ISP_LICENSES = ("isp_lite6", "isp_pro6", "isp_host6")
|
|
|
|
|
|
def build_size(cpu: int, ram: int, disk: int, line: str = "hp") -> str:
|
|
"""Собирает slug тарифа и проверяет сочетание по правилам платформы.
|
|
|
|
build_size(4, 8, 120, "hp") -> 'c4-m8-d120-hp'
|
|
"""
|
|
if line not in PLAN_LINES:
|
|
raise ValueError(f"нет линейки {line}, есть {', '.join(PLAN_LINES)}")
|
|
allowed = CPU_RAM.get(line, {})
|
|
if cpu not in allowed:
|
|
raise ValueError(f"в линейке {line} нет варианта на {cpu} ядер, "
|
|
f"доступны {sorted(allowed)}")
|
|
if ram not in allowed[cpu]:
|
|
raise ValueError(f"при {cpu} ядрах в линейке {line} память может быть "
|
|
f"{allowed[cpu]}, а не {ram}")
|
|
low, high = DISK_RANGE[line]
|
|
if not (low <= disk <= high) or disk % 10:
|
|
raise ValueError(f"диск в линейке {line} от {low} до {high} ГБ с шагом 10, а не {disk}")
|
|
return f"c{cpu}-m{ram}-d{disk}-{line}"
|
|
|
|
|
|
def parse_size(slug: str) -> dict:
|
|
"""Разбирает slug обратно в понятные значения."""
|
|
if slug in GPU_SIZES:
|
|
name, cpu, ram, disk, price = GPU_SIZES[slug]
|
|
return {"slug": slug, "gpu": name, "vcpus": cpu, "memory_gb": ram,
|
|
"disk_gb": disk, "price_hour": price, "line": "gpu"}
|
|
parts = slug.split("-")
|
|
try:
|
|
return {
|
|
"slug": slug,
|
|
"vcpus": int(parts[0].lstrip("c")),
|
|
"memory_gb": int(parts[1].lstrip("m")),
|
|
"disk_gb": int(parts[2].lstrip("d")),
|
|
"line": parts[3] if len(parts) > 3 else "",
|
|
"gpu": None,
|
|
}
|
|
except (IndexError, ValueError):
|
|
return {"slug": slug}
|
|
|
|
|
|
def gpu_catalog() -> list[dict]:
|
|
"""Тарифы с видеокартой списком, отсортированные по цене."""
|
|
out = [{"slug": s, "gpu": v[0], "vcpus": v[1], "memory_gb": v[2],
|
|
"disk_gb": v[3], "price_hour": v[4]} for s, v in GPU_SIZES.items()]
|
|
return sorted(out, key=lambda x: x["price_hour"])
|