fix(delete): считать уже отсутствующий в Proxmox ресурс успешно удалённым
This commit is contained in:
@@ -7,6 +7,7 @@ import string
|
|||||||
import time
|
import time
|
||||||
|
|
||||||
from proxmoxer import ProxmoxAPI
|
from proxmoxer import ProxmoxAPI
|
||||||
|
from proxmoxer.core import ResourceException
|
||||||
|
|
||||||
from .config import settings
|
from .config import settings
|
||||||
|
|
||||||
@@ -26,7 +27,7 @@ def _client() -> ProxmoxAPI:
|
|||||||
|
|
||||||
|
|
||||||
def gen_password(length: int = 14) -> str:
|
def gen_password(length: int = 14) -> str:
|
||||||
"""Генерирует пароль из букв и цифр (для VM-cloud-init, если пользователь не задал)."""
|
"""Генерирует пароль из букв и цифр."""
|
||||||
alphabet = string.ascii_letters + string.digits
|
alphabet = string.ascii_letters + string.digits
|
||||||
return "".join(random.choice(alphabet) for _ in range(length))
|
return "".join(random.choice(alphabet) for _ in range(length))
|
||||||
|
|
||||||
@@ -64,25 +65,9 @@ def create_lxc(
|
|||||||
storage: str = "local-lvm",
|
storage: str = "local-lvm",
|
||||||
node: str = None,
|
node: str = None,
|
||||||
) -> str:
|
) -> str:
|
||||||
"""Создаёт LXC из архивного шаблона и запускает его.
|
"""Создаёт LXC из архива: ostemplate → passwd → resize → start."""
|
||||||
|
|
||||||
Шаги (каждый — отдельный запрос к Proxmox API):
|
|
||||||
1. Создаёт LXC через ``ostemplate`` (без запуска).
|
|
||||||
2. Задаёт root-пароль через ``/passwd``.
|
|
||||||
3. Создаёт rootfs указанного размера через ``/resize``.
|
|
||||||
4. Запускает контейнер через ``/status/start``.
|
|
||||||
|
|
||||||
``password`` приходит из формы (клиент задаёт сам) и сохраняется в БД
|
|
||||||
для одноразового показа в карточке инстанса.
|
|
||||||
|
|
||||||
LXC создаётся ТОЛЬКО из архивного шаблона (vztmpl на storage).
|
|
||||||
Клонирование существующего LXC по VMID не поддерживается.
|
|
||||||
"""
|
|
||||||
node = node or settings.pve_node
|
node = node or settings.pve_node
|
||||||
px = _client()
|
px = _client()
|
||||||
|
|
||||||
# 1. Создаём LXC через ostemplate. start=0 — не запускаем, чтобы успеть
|
|
||||||
# задать пароль и диск нужного размера до старта.
|
|
||||||
upid = px.nodes(node).lxc.post(
|
upid = px.nodes(node).lxc.post(
|
||||||
vmid=new_vmid,
|
vmid=new_vmid,
|
||||||
hostname=name,
|
hostname=name,
|
||||||
@@ -96,23 +81,14 @@ def create_lxc(
|
|||||||
start=0,
|
start=0,
|
||||||
)
|
)
|
||||||
_wait_task(px, node, upid)
|
_wait_task(px, node, upid)
|
||||||
|
|
||||||
# 2. Задаём пароль через отдельный endpoint /passwd.
|
|
||||||
# Поле password в POST /lxc на некоторых версиях Proxmox игнорируется,
|
|
||||||
# поэтому дублируем через /passwd — это идемпотентная операция.
|
|
||||||
px.nodes(node).lxc(new_vmid).passwd.post(password=password)
|
px.nodes(node).lxc(new_vmid).passwd.post(password=password)
|
||||||
|
|
||||||
# 3. Создаём rootfs указанного размера (storage:size_G).
|
|
||||||
px.nodes(node).lxc(new_vmid).resize.put(
|
px.nodes(node).lxc(new_vmid).resize.put(
|
||||||
disk="rootfs",
|
disk="rootfs",
|
||||||
size=f"{storage}:{disk_gb}",
|
size=f"{storage}:{disk_gb}",
|
||||||
)
|
)
|
||||||
|
|
||||||
# 4. Запускаем контейнер.
|
|
||||||
upid = px.nodes(node).lxc(new_vmid).status.start.post()
|
upid = px.nodes(node).lxc(new_vmid).status.start.post()
|
||||||
if upid:
|
if upid:
|
||||||
_wait_task(px, node, upid)
|
_wait_task(px, node, upid)
|
||||||
|
|
||||||
return password
|
return password
|
||||||
|
|
||||||
|
|
||||||
@@ -137,8 +113,7 @@ _DISK_KEYS = ("scsi0", "virtio0", "ide0", "sata0")
|
|||||||
def _disk_size_from_config(cfg: dict) -> int:
|
def _disk_size_from_config(cfg: dict) -> int:
|
||||||
"""Извлекает размер диска VM в ГБ."""
|
"""Извлекает размер диска VM в ГБ."""
|
||||||
for key in _DISK_KEYS:
|
for key in _DISK_KEYS:
|
||||||
value = cfg.get(key, "")
|
match = re.search(r"size=(\d+)G", cfg.get(key, ""))
|
||||||
match = re.search(r"size=(\d+)G", value)
|
|
||||||
if match:
|
if match:
|
||||||
return int(match.group(1))
|
return int(match.group(1))
|
||||||
return 10
|
return 10
|
||||||
@@ -163,7 +138,7 @@ def list_vm_templates(node: str = None) -> list:
|
|||||||
|
|
||||||
|
|
||||||
def list_lxc_templates(node: str = None) -> list:
|
def list_lxc_templates(node: str = None) -> list:
|
||||||
"""Возвращает архивные LXC-шаблоны (vztmpl) из доступных хранилищ."""
|
"""Возвращает архивные LXC-шаблоны (vztmpl)."""
|
||||||
node = node or settings.pve_node
|
node = node or settings.pve_node
|
||||||
px = _client()
|
px = _client()
|
||||||
result = []
|
result = []
|
||||||
@@ -276,6 +251,16 @@ def guest_action(guest_type: str, vmid: int, action: str, node: str = None) -> N
|
|||||||
getattr(endpoint.status, action).post()
|
getattr(endpoint.status, action).post()
|
||||||
|
|
||||||
|
|
||||||
|
def _is_missing_guest_error(exc: Exception) -> bool:
|
||||||
|
"""Проверяет, что Proxmox сообщает об отсутствии VM/LXC."""
|
||||||
|
message = str(exc).lower()
|
||||||
|
return (
|
||||||
|
"does not exist" in message
|
||||||
|
or "not found" in message
|
||||||
|
or "configuration file" in message and "does not exist" in message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _wait_guest_stopped(px: ProxmoxAPI, guest_type: str, vmid: int, node: str, timeout: int = 180) -> None:
|
def _wait_guest_stopped(px: ProxmoxAPI, guest_type: str, vmid: int, node: str, timeout: int = 180) -> None:
|
||||||
"""Ожидает, пока VM или LXC перейдёт в состояние stopped."""
|
"""Ожидает, пока VM или LXC перейдёт в состояние stopped."""
|
||||||
endpoint = px.nodes(node).qemu(vmid) if guest_type == "vm" else px.nodes(node).lxc(vmid)
|
endpoint = px.nodes(node).qemu(vmid) if guest_type == "vm" else px.nodes(node).lxc(vmid)
|
||||||
@@ -289,23 +274,44 @@ def _wait_guest_stopped(px: ProxmoxAPI, guest_type: str, vmid: int, node: str, t
|
|||||||
|
|
||||||
|
|
||||||
def delete_guest(guest_type: str, vmid: int, node: str = None) -> None:
|
def delete_guest(guest_type: str, vmid: int, node: str = None) -> None:
|
||||||
"""Останавливает работающий инстанс и удаляет его."""
|
"""Удаляет VM/LXC.
|
||||||
|
|
||||||
|
Если ресурс уже удалён вручную в Proxmox, операция считается успешной.
|
||||||
|
Это позволяет backend удалить устаревшую запись из собственной БД.
|
||||||
|
"""
|
||||||
node = node or settings.pve_node
|
node = node or settings.pve_node
|
||||||
px = _client()
|
px = _client()
|
||||||
endpoint = px.nodes(node).qemu(vmid) if guest_type == "vm" else px.nodes(node).lxc(vmid)
|
endpoint = px.nodes(node).qemu(vmid) if guest_type == "vm" else px.nodes(node).lxc(vmid)
|
||||||
status = endpoint.status.current.get().get("status")
|
|
||||||
|
try:
|
||||||
|
status = endpoint.status.current.get().get("status")
|
||||||
|
except ResourceException as exc:
|
||||||
|
if _is_missing_guest_error(exc):
|
||||||
|
logger.info("%s %s уже отсутствует в Proxmox — считаем удалённым", guest_type, vmid)
|
||||||
|
return
|
||||||
|
raise
|
||||||
|
|
||||||
if status == "running":
|
if status == "running":
|
||||||
try:
|
try:
|
||||||
shutdown_upid = endpoint.status.shutdown.post(timeout=60)
|
shutdown_upid = endpoint.status.shutdown.post(timeout=60)
|
||||||
if shutdown_upid:
|
if shutdown_upid:
|
||||||
_wait_task(px, node, shutdown_upid, timeout=120)
|
_wait_task(px, node, shutdown_upid, timeout=120)
|
||||||
|
except ResourceException as exc:
|
||||||
|
if not _is_missing_guest_error(exc):
|
||||||
|
logger.warning("Мягкая остановка %s %s не удалась: %s", guest_type, vmid, exc)
|
||||||
|
endpoint.status.stop.post()
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
logger.warning("Мягкая остановка %s %s не удалась: %s", guest_type, vmid, exc)
|
logger.warning("Мягкая остановка %s %s не удалась: %s", guest_type, vmid, exc)
|
||||||
endpoint.status.stop.post()
|
endpoint.status.stop.post()
|
||||||
_wait_guest_stopped(px, guest_type, vmid, node)
|
_wait_guest_stopped(px, guest_type, vmid, node)
|
||||||
|
|
||||||
upid = endpoint.delete()
|
try:
|
||||||
|
upid = endpoint.delete()
|
||||||
|
except ResourceException as exc:
|
||||||
|
if _is_missing_guest_error(exc):
|
||||||
|
logger.info("%s %s исчез во время удаления — считаем удалённым", guest_type, vmid)
|
||||||
|
return
|
||||||
|
raise
|
||||||
if upid:
|
if upid:
|
||||||
_wait_task(px, node, upid)
|
_wait_task(px, node, upid)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user