Files
Proxmox-VPS-Panel/backend/app/routers/instances.py
T

232 lines
11 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import List
from fastapi import APIRouter, Depends, HTTPException, BackgroundTasks
from sqlalchemy.orm import Session, joinedload
from .. import models, schemas, proxmox_client as pve
from ..config import settings
from ..database import get_db
from ..deps import get_current_user
router = APIRouter(prefix="/instances", tags=["instances"])
@router.get("", response_model=List[schemas.InstanceOut])
def list_my_instances(db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
query = db.query(models.Instance).options(joinedload(models.Instance.template))
query = query.filter(models.Instance.status != models.InstanceStatus.deleted)
if user.role != models.Role.admin:
query = query.filter(models.Instance.owner_id == user.id)
return query.order_by(models.Instance.created_at.desc()).all()
def _provision(instance_id: int, template_id: int, node: str):
"""Фоновое создание VPS через Proxmox API."""
from ..database import SessionLocal
db = SessionLocal()
try:
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
if not instance:
return
template = db.query(models.Template).filter(models.Template.id == template_id).first()
if not template:
instance.status = models.InstanceStatus.error
instance.root_password = "ERROR: шаблон не найден"
db.commit()
return
try:
if template.guest_type == models.GuestType.vm:
clean_name = ''.join(c for c in instance.name.strip() if c.isalnum() or c == '-').lower() or 'vm'
pve.clone_vm(template.source_vmid, instance.vmid, clean_name, node)
pve.resize_vm(instance.vmid, template.cores, template.memory_mb, node)
pve.resize_disk(instance.vmid, template.disk_gb, node)
if instance.ciuser and instance.root_password:
pve.configure_cloud_init(instance.vmid, instance.ciuser, instance.root_password, node)
pve.guest_action("vm", instance.vmid, "start", node)
else:
clean_name = ''.join(c for c in instance.name.strip() if c.isalnum() or c == '-').lower() or 'lxc'
password = pve.create_lxc(
new_vmid=instance.vmid, name=clean_name,
template_volid=template.source_template,
cores=template.cores, memory_mb=template.memory_mb,
disk_gb=template.disk_gb, node=node,
)
instance.root_password = password
pve.guest_action("lxc", instance.vmid, "start", node)
instance.status = models.InstanceStatus.running
except Exception as exc:
instance.status = models.InstanceStatus.error
instance.root_password = f"ERROR: {exc}"
db.commit()
finally:
db.close()
@router.post("", response_model=schemas.InstanceOut)
def create_instance(
payload: schemas.InstanceCreate,
background_tasks: BackgroundTasks,
db: Session = Depends(get_db),
user: models.User = Depends(get_current_user),
):
template = db.query(models.Template).filter(
models.Template.id == payload.template_id, models.Template.is_active == True
).first()
if not template:
raise HTTPException(status_code=404, detail="Шаблон не найден")
for _ in range(100):
try:
vmid = pve.get_next_vmid()
except Exception as exc:
raise HTTPException(status_code=502, detail=f"Proxmox недоступен: {exc}")
exists = db.query(models.Instance).filter(
models.Instance.vmid == vmid,
models.Instance.status != models.InstanceStatus.deleted,
).first()
if not exists:
break
else:
raise HTTPException(status_code=409, detail="Не удалось найти свободный VMID")
clean_name = ''.join(c for c in payload.name.strip() if c.isalnum() or c == '-').lower() or 'vps'
# Proxmox переиспользует освободившиеся vmid — вычищаем мёртвые записи,
# иначе INSERT упадёт с duplicate key по ix_instances_vmid
stale = db.query(models.Instance).filter(
models.Instance.vmid == vmid,
models.Instance.status.in_([
models.InstanceStatus.error,
models.InstanceStatus.deleted,
models.InstanceStatus.deleting,
]),
).all()
for s in stale:
db.delete(s)
if stale:
db.commit()
instance = models.Instance(
name=clean_name, vmid=vmid, node=settings.pve_node,
guest_type=template.guest_type, status=models.InstanceStatus.creating,
owner_id=user.id, template_id=template.id,
ciuser=payload.ciuser or None,
root_password=payload.cipassword or None,
)
db.add(instance)
db.commit()
db.refresh(instance)
background_tasks.add_task(_provision, instance.id, template.id, settings.pve_node)
return instance
@router.get("/{instance_id}", response_model=schemas.InstanceOut)
def get_instance(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
if not instance:
raise HTTPException(status_code=404, detail="VPS не найден")
return instance
@router.get("/{instance_id}/status")
def get_instance_status(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
if not instance:
raise HTTPException(status_code=404, detail="VPS не найден")
try:
return pve.get_status(instance.guest_type.value, instance.vmid, instance.node)
except Exception as exc:
raise HTTPException(status_code=502, detail=f"Proxmox недоступен: {exc}")
@router.get("/{instance_id}/ip")
def get_instance_ip(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
if not instance:
raise HTTPException(status_code=404, detail="VPS не найден")
try:
ip = pve.get_instance_ip(instance.vmid, instance.node)
return {"ip": ip}
except Exception as exc:
return {"ip": f"ошибка: {exc}"}
@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)):
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
if not instance:
raise HTTPException(status_code=404, detail="VPS не найден")
try:
pve.guest_action(instance.guest_type.value, instance.vmid, payload.action, instance.node)
except Exception as exc:
raise HTTPException(status_code=502, detail=f"Ошибка: {exc}")
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}
@router.get("/{instance_id}/ip")
def get_instance_ip(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
"""Возвращает IP-адрес инстанса."""
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
if not instance:
raise HTTPException(status_code=404, detail="VPS не найден")
if user.role != models.Role.admin and instance.owner_id != user.id:
raise HTTPException(status_code=403, detail="Нет доступа")
try:
ip = pve.get_instance_ip(instance.vmid, instance.node)
return {"ip": ip}
except Exception as exc:
return {"ip": f"ошибка: {exc}"}
@router.get("/{instance_id}/ip")
def get_instance_ip(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
"""Возвращает IP-адрес инстанса."""
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
if not instance:
raise HTTPException(status_code=404, detail="VPS не найден")
if user.role != models.Role.admin and instance.owner_id != user.id:
raise HTTPException(status_code=403, detail="Нет доступа")
try:
ip = pve.get_instance_ip(instance.vmid, instance.node)
return {"ip": ip}
except Exception as exc:
return {"ip": f"ошибка: {exc}"}
@router.get("/{instance_id}/live")
def get_instance_live(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
"""Live CPU/RAM/Memory показатели."""
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
if not instance:
raise HTTPException(status_code=404, detail="VPS не найден")
try:
return pve.get_live_stats(instance.vmid, instance.node)
except Exception as exc:
return {"cpu": 0, "mem_used": 0, "mem_total": 0, "uptime": 0, "status": "error", "error": str(exc)}
@router.delete("/{instance_id}")
def delete_instance(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
if not instance:
raise HTTPException(status_code=404, detail="VPS не найден")
try:
pve.delete_guest(instance.guest_type.value, instance.vmid, instance.node)
except Exception as exc:
raise HTTPException(status_code=502, detail=f"Ошибка удаления: {exc}")
instance.status = models.InstanceStatus.deleted
db.commit()
return {"ok": True}
@router.get("/{instance_id}/console")
def instance_console(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
if not instance:
raise HTTPException(status_code=404, detail="VPS не найден")
try:
ticket = pve.get_vnc_ticket(instance.guest_type.value, instance.vmid, instance.node)
except Exception as exc:
raise HTTPException(status_code=502, detail=f"Консоль недоступна: {exc}")
ticket["instance_id"] = instance.id
return ticket