From aeba47b7aa195618c430bd4507250e32d9eccb0c Mon Sep 17 00:00:00 2001 From: host Date: Sun, 9 Aug 2026 16:14:19 +0300 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=BE=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=20=D0=B8=D0=BD=D1=81=D1=82=D0=B0=D0=BD=D1=81=D0=BE=D0=B2=20?= =?UTF-8?q?=D0=B8=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA=20VMID?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/app/routers/instances.py | 99 ++++++++++++++++---------------- 1 file changed, 51 insertions(+), 48 deletions(-) diff --git a/backend/app/routers/instances.py b/backend/app/routers/instances.py index 0f02367..562ef73 100644 --- a/backend/app/routers/instances.py +++ b/backend/app/routers/instances.py @@ -8,62 +8,48 @@ 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 logger = logging.getLogger(__name__) router = APIRouter(prefix="/instances", tags=["instances"]) @router.get("", response_model=list[schemas.InstanceOut]) -def list_instances( - db: Session = Depends(get_db), - user: models.User = Depends(get_current_user), -): +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 - ) + 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) - query = query.order_by(models.Instance.created_at.desc()) - return list(db.scalars(query)) + return list(db.scalars(query.order_by(models.Instance.created_at.desc()))) def _save_error(db: Session, instance: models.Instance, exc: Exception) -> None: - """Помечает инстанс ошибочным, не сохраняя секреты или детали API.""" + """Помечает инстанс ошибочным без сохранения деталей 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), - ) - ) +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="Активный шаблон не найден") - vmid = pve.next_vmid() + 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, + 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() @@ -72,23 +58,14 @@ def create_instance( try: if template.guest_type == models.GuestType.vm: pve.provision_vm( - source_vmid=template.source_vmid, - vmid=vmid, - name=payload.name, - cores=template.cores, - memory_mb=template.memory_mb, - disk_gb=template.disk_gb, - username=payload.username, - password=payload.password, + source_vmid=template.source_vmid, vmid=vmid, name=payload.name, + cores=template.cores, memory_mb=template.memory_mb, disk_gb=template.disk_gb, + username=payload.username, password=payload.password, ) else: pve.provision_lxc( - source_template=template.source_template, - vmid=vmid, - name=payload.name, - cores=template.cores, - memory_mb=template.memory_mb, - disk_gb=template.disk_gb, + source_template=template.source_template, vmid=vmid, name=payload.name, + cores=template.cores, memory_mb=template.memory_mb, disk_gb=template.disk_gb, password=payload.password, ) except Exception as exc: @@ -102,3 +79,29 @@ def create_instance( **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 и отмечает запись deleted.""" + 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="Инстанс не найден") + 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}