Обновление файла
This commit is contained in:
@@ -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="Шаблон не найден")
|
||||
|
||||
try:
|
||||
vmid = 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)
|
||||
.first()
|
||||
)
|
||||
if active:
|
||||
raise HTTPException(status_code=409, detail=f"VMID {vmid} уже используется в панели")
|
||||
# Получаем VMID, который точно свободен: nextid из Proxmox + проверка БД.
|
||||
# Если в БД висит «удалённая» запись с этим VMID (мусор), пропускаем —
|
||||
# запрашиваем следующий. Proxmox при повторном nextid может вернуть тот же
|
||||
# VMID, потому что он сам не знает про нашу БД. Поэтому цикл по Proxmox
|
||||
# ограничен сверху, а грязные записи в БД лучше чистить заранее
|
||||
# (разовая операция, см. README).
|
||||
vmid = None
|
||||
for _ in range(5):
|
||||
try:
|
||||
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 == 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(
|
||||
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}
|
||||
|
||||
Reference in New Issue
Block a user