import logging from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.orm import Session from .. import models, proxmox_client as pve, schemas from ..config import settings from ..database import get_db from ..deps import get_current_user from ..proxmox_actions import delete_guest, get_live_stats, guest_action logger = logging.getLogger(__name__) router = APIRouter(prefix="/instances", tags=["instances"]) def _get_owned_instance(db: Session, user: models.User, instance_id: int) -> models.Instance: """Возвращает инстанс с проверкой владельца.""" instance = db.get(models.Instance, instance_id) if instance is None or instance.status == models.InstanceStatus.deleted: raise HTTPException(status_code=404, detail="Инстанс не найден") if user.role != models.UserRole.admin and instance.owner_id != user.id: raise HTTPException(status_code=404, detail="Инстанс не найден") return instance @router.get("", response_model=list[schemas.InstanceOut]) def list_instances(db: Session = Depends(get_db), user: models.User = Depends(get_current_user)): """Клиент видит только свои инстансы, администратор — все.""" query = select(models.Instance).where(models.Instance.status != models.InstanceStatus.deleted) if user.role != models.UserRole.admin: query = query.where(models.Instance.owner_id == user.id) return list(db.scalars(query.order_by(models.Instance.created_at.desc()))) @router.get("/{instance_id}/stats") def instance_stats(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)): """Возвращает актуальные CPU/RAM/диск/uptime из Proxmox.""" instance = _get_owned_instance(db, user, instance_id) try: return get_live_stats(instance.guest_type.value, instance.vmid) except Exception as exc: logger.warning("Ошибка статистики instance_id=%s: %s", instance_id, exc) raise HTTPException(status_code=502, detail="Не удалось получить статистику Proxmox") @router.post("/{instance_id}/action") def instance_action(instance_id: int, payload: schemas.InstanceAction, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)): """Запускает, останавливает или перезапускает VM/LXC.""" instance = _get_owned_instance(db, user, instance_id) try: guest_action(instance.guest_type.value, instance.vmid, payload.action) except Exception as exc: logger.warning("Ошибка action=%s instance_id=%s: %s", payload.action, instance_id, exc) raise HTTPException(status_code=502, detail="Не удалось выполнить действие в Proxmox") if payload.action == "start": instance.status = models.InstanceStatus.running elif payload.action in {"stop", "shutdown"}: instance.status = models.InstanceStatus.stopped db.commit() return {"ok": True, "action": payload.action} def _save_error(db: Session, instance: models.Instance, exc: Exception) -> None: """Помечает инстанс ошибочным без выдачи деталей Proxmox.""" instance.status = models.InstanceStatus.error db.commit() logger.exception("Ошибка провижининга instance_id=%s: %s", instance.id, exc) @router.post("", response_model=schemas.InstanceCreateOut, status_code=status.HTTP_201_CREATED) def create_instance(payload: schemas.InstanceCreate, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)): """Создаёт и запускает VM/LXC из активного шаблона.""" template = db.scalar(select(models.Template).where(models.Template.id == payload.template_id, models.Template.is_active.is_(True))) if template is None: raise HTTPException(status_code=404, detail="Активный шаблон не найден") try: vmid = pve.next_vmid() except Exception as exc: logger.exception("Не удалось получить VMID из Proxmox: %s", exc) raise HTTPException(status_code=502, detail="Proxmox недоступен") instance = models.Instance(name=payload.name, vmid=vmid, node=settings.pve_node, guest_type=template.guest_type, status=models.InstanceStatus.creating, owner_id=user.id, template_id=template.id) db.add(instance); db.commit(); db.refresh(instance) try: if template.guest_type == models.GuestType.vm: pve.provision_vm(template.source_vmid, vmid, payload.name, template.cores, template.memory_mb, template.disk_gb, payload.username, payload.password) else: pve.provision_lxc(template.source_template, vmid, payload.name, template.cores, template.memory_mb, template.disk_gb, payload.password) except Exception as exc: _save_error(db, instance, exc) raise HTTPException(status_code=502, detail="Не удалось создать инстанс в Proxmox") instance.status = models.InstanceStatus.running; db.commit(); db.refresh(instance) return schemas.InstanceCreateOut(**schemas.InstanceOut.model_validate(instance).model_dump(), initial_password=payload.password) @router.delete("/{instance_id}") def delete_instance(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)): """Удаляет принадлежащую пользователю VM/LXC.""" instance = _get_owned_instance(db, user, instance_id) if instance.vmid is None: raise HTTPException(status_code=409, detail="У инстанса отсутствует VMID") instance.status = models.InstanceStatus.deleting; db.commit() try: delete_guest(instance.guest_type.value, instance.vmid) except Exception as exc: instance.status = models.InstanceStatus.error; db.commit() logger.exception("Ошибка удаления instance_id=%s: %s", instance.id, exc) raise HTTPException(status_code=502, detail="Не удалось удалить инстанс в Proxmox") instance.status = models.InstanceStatus.deleted; db.commit() return {"ok": True}