Обновление файла

This commit is contained in:
2026-08-09 22:48:08 +03:00
parent aac83617dc
commit 3e110c4e30
+52 -18
View File
@@ -102,7 +102,18 @@ def create_instance(
db: Session = Depends(get_db), db: Session = Depends(get_db),
user: models.User = Depends(get_current_user), user: models.User = Depends(get_current_user),
): ):
"""Регистрирует один инстанс и запускает его создание в фоне.""" """Регистрирует один инстанс и запускает его создание в фоне.
Бизнес-логика VMID:
1. Запрашиваем ``nextid`` у Proxmox (он возвращает следующий свободный VMID
в кластере — то есть тот, что реально не занят ни в Proxmox).
2. Проверяем в БД, что этот VMID не используется активным инстансом
(``status != deleted``). Если есть запись со статусом deleted — это
«мусор» от старых удалений, который мы НЕ должны учитывать (nextid
от Proxmox пришёл, значит в Proxmox этот VMID свободен).
3. Если конфликт — запрашиваем новый VMID через ``nextid``
(онлайн-цикл до 5 попыток).
"""
db.query(models.User).filter(models.User.id == user.id).with_for_update().one() db.query(models.User).filter(models.User.id == user.id).with_for_update().one()
pending = ( pending = (
db.query(models.Instance) db.query(models.Instance)
@@ -123,19 +134,36 @@ def create_instance(
if not template: if not template:
raise HTTPException(status_code=404, detail="Шаблон не найден") raise HTTPException(status_code=404, detail="Шаблон не найден")
try: # Получаем VMID, который точно свободен: nextid из Proxmox + проверка БД.
vmid = pve.get_next_vmid() # Если в БД висит «удалённая» запись с этим VMID (мусор), пропускаем —
except Exception as exc: # запрашиваем следующий. Proxmox при повторном nextid может вернуть тот же
logger.error("Proxmox недоступен при запросе nextid: %s", exc) # VMID, потому что он сам не знает про нашу БД. Поэтому цикл по Proxmox
raise HTTPException(status_code=502, detail=f"Proxmox недоступен: {exc}") # ограничен сверху, а грязные записи в БД лучше чистить заранее
# (разовая операция, см. README).
active = ( vmid = None
db.query(models.Instance) for _ in range(5):
.filter(models.Instance.vmid == vmid, models.Instance.status != models.InstanceStatus.deleted) try:
.first() candidate = pve.get_next_vmid()
) except Exception as exc:
if active: logger.error("Proxmox недоступен при запросе nextid: %s", exc)
raise HTTPException(status_code=409, detail=f"VMID {vmid} уже используется в панели") raise HTTPException(status_code=502, detail=f"Proxmox недоступен: {exc}")
active = (
db.query(models.Instance)
.filter(
models.Instance.vmid == candidate,
models.Instance.status != models.InstanceStatus.deleted,
)
.first()
)
if not active:
vmid = candidate
break
if vmid is None:
raise HTTPException(
status_code=409,
detail="Не удалось получить свободный VMID — несколько попыток подряд вернули занятый VMID. "
"Очистите БД от записей со статусом 'deleted' или повторите позже.",
)
instance = models.Instance( instance = models.Instance(
name=_sanitize_name(payload.name), name=_sanitize_name(payload.name),
@@ -231,15 +259,19 @@ def instance_action(instance_id: int, payload: schemas.InstanceAction, db: Sessi
@router.delete("/{instance_id}") @router.delete("/{instance_id}")
def delete_instance(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)): def delete_instance(instance_id: int, db: Session = Depends(get_db), user: models.User = Depends(get_current_user)):
"""Останавливает работающую VM/LXC и удаляет её из Proxmox.""" """Останавливает работающую VM/LXC и удаляет её из Proxmox.
После успешного удаления в Proxmox — физически удаляет запись из БД,
чтобы VMID можно было использовать повторно (``nextid`` от Proxmox всегда
вернёт свободный ID, а наша БД не должна блокировать его из-за мусора).
"""
instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first() instance = db.query(models.Instance).filter(models.Instance.id == instance_id).first()
if not instance: if not instance:
raise HTTPException(status_code=404, detail="VPS не найден") raise HTTPException(status_code=404, detail="VPS не найден")
_ensure_owner(instance, user) _ensure_owner(instance, user)
if instance.status == models.InstanceStatus.deleting: if instance.status == models.InstanceStatus.deleting:
raise HTTPException(status_code=409, detail="Инстанс уже удаляется") raise HTTPException(status_code=409, detail="Инстанс уже удаляется")
instance.status = models.InstanceStatus.deleting vmid_for_log = instance.vmid # сохраняем для логов до удаления ORM-объекта
db.commit()
try: try:
pve.delete_guest(instance.guest_type.value, instance.vmid, instance.node) pve.delete_guest(instance.guest_type.value, instance.vmid, instance.node)
except Exception as exc: except Exception as exc:
@@ -247,6 +279,8 @@ def delete_instance(instance_id: int, db: Session = Depends(get_db), user: model
instance.status = models.InstanceStatus.error instance.status = models.InstanceStatus.error
db.commit() db.commit()
raise HTTPException(status_code=502, detail=f"Ошибка удаления: {exc}") raise HTTPException(status_code=502, detail=f"Ошибка удаления: {exc}")
instance.status = models.InstanceStatus.deleted # Физическое удаление записи — VMID освобождается для повторного использования.
db.delete(instance)
db.commit() db.commit()
logger.info("Инстанс vmid=%s удалён из БД (был id=%s)", vmid_for_log, instance_id)
return {"ok": True} return {"ok": True}