119 lines
3.6 KiB
Python
119 lines
3.6 KiB
Python
"""openstacksdk connection. IAM token is 24h; sdk refreshes on authorize()."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Iterator
|
|
from typing import Any
|
|
|
|
from gpu_rent.config import Config
|
|
from gpu_rent.errors import CloudError
|
|
|
|
RESOURCE_TAG = "gpu-rent"
|
|
COMPUTE_MICROVERSION = "2.72"
|
|
|
|
|
|
def connect(cfg: Config):
|
|
try:
|
|
import openstack
|
|
except ImportError as exc:
|
|
raise CloudError("Нет openstacksdk. Переустанови пакет: pip install -e .") from exc
|
|
|
|
try:
|
|
conn = openstack.connect(
|
|
auth_url=cfg.os_auth_url,
|
|
project_id=cfg.os_project_id,
|
|
username=cfg.os_username,
|
|
password=cfg.os_password,
|
|
user_domain_name=cfg.os_user_domain_name,
|
|
project_domain_name=cfg.os_user_domain_name,
|
|
region_name=cfg.os_region_name,
|
|
identity_api_version="3",
|
|
interface="public",
|
|
compute_api_version=COMPUTE_MICROVERSION,
|
|
app_name="gpu-rent",
|
|
app_version="0.1.0",
|
|
)
|
|
conn.authorize()
|
|
except Exception as exc:
|
|
raise CloudError(
|
|
"Keystone не выдал токен. Проверь OS_USERNAME / OS_PASSWORD / "
|
|
"OS_PROJECT_ID / OS_USER_DOMAIN_NAME (номер аккаунта). "
|
|
"Не используй X-Token панели. Подробности: docs/setup.md. "
|
|
f"Ошибка SDK: {exc}"
|
|
) from exc
|
|
return conn
|
|
|
|
|
|
def _obj_dict(obj: Any) -> dict[str, Any]:
|
|
if obj is None:
|
|
return {}
|
|
if hasattr(obj, "to_dict"):
|
|
try:
|
|
return obj.to_dict(computed=False)
|
|
except TypeError:
|
|
return obj.to_dict()
|
|
if isinstance(obj, dict):
|
|
return obj
|
|
return {"repr": repr(obj)}
|
|
|
|
|
|
def compute_quotas(conn) -> dict[str, Any]:
|
|
project = conn.current_project_id
|
|
try:
|
|
quota = conn.compute.get_quota_set(project)
|
|
return _obj_dict(quota)
|
|
except Exception:
|
|
try:
|
|
return dict(conn.get_compute_quotas(project) or {})
|
|
except Exception as exc:
|
|
raise CloudError(f"Не прочитать compute quota: {exc}") from exc
|
|
|
|
|
|
def volume_quotas(conn) -> dict[str, Any]:
|
|
project = conn.current_project_id
|
|
try:
|
|
quota = conn.block_storage.get_quota_set(project)
|
|
return _obj_dict(quota)
|
|
except Exception:
|
|
try:
|
|
return dict(conn.get_volume_quotas(project) or {})
|
|
except Exception as exc:
|
|
return {"error": str(exc)}
|
|
|
|
|
|
def iter_flavors(conn) -> Iterator[Any]:
|
|
yield from conn.compute.flavors(details=True)
|
|
|
|
|
|
def iter_volume_types(conn) -> Iterator[Any]:
|
|
yield from conn.block_storage.types()
|
|
|
|
|
|
def iter_images(conn) -> Iterator[Any]:
|
|
yield from conn.image.images()
|
|
|
|
|
|
def find_tagged_servers(conn) -> list[Any]:
|
|
servers = []
|
|
for server in conn.compute.servers(details=True):
|
|
tags = set(getattr(server, "tags", None) or [])
|
|
name = (getattr(server, "name", "") or "").lower()
|
|
if RESOURCE_TAG in tags or name.startswith("gpu-rent"):
|
|
servers.append(server)
|
|
return servers
|
|
|
|
|
|
def find_volumes_by_name(conn, name: str) -> list[Any]:
|
|
found = []
|
|
for volume in conn.block_storage.volumes():
|
|
if getattr(volume, "name", None) == name:
|
|
found.append(volume)
|
|
return found
|
|
|
|
|
|
def find_snapshot_by_name(conn, name: str) -> Any | None:
|
|
for snap in conn.block_storage.snapshots():
|
|
if getattr(snap, "name", None) == name:
|
|
return snap
|
|
return None
|