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

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