"""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