fix: re-download cached import images when checksum is provided
Some checks failed
Deploy to Production / Trigger production update (push) Has been cancelled
Backend Tests / pytest (Python 3.12) (push) Has been cancelled

volume_exists() only checked file presence, not content. A stale image
from a previous download (different URL/version) would be silently reused,
causing templates and VMs to be created from unpatched images even when
the mirror's checksum pointed to a newer patched build.

Add delete_volume() to ProxmoxClient storage mixin, and update both
_do_image_download_sync and _do_image_template_sync to delete the cached
volume and re-download whenever a checksum is provided, letting Proxmox
verify the new download server-side.
This commit is contained in:
2026-08-14 16:19:41 +05:00
parent 3540291c0e
commit 8ff757f8c1
2 changed files with 37 additions and 7 deletions

View File

@@ -886,12 +886,18 @@ def _do_image_download_sync(task_id: int, server_id: int, node: str, storage: st
else:
dl_filename = filename
# Идемпотентность: если файл уже есть в хранилище — не качаем повторно
# Идемпотентность: если файл уже есть в хранилище — не качаем повторно,
# но при наличии контрольной суммы удаляем кэш и перекачиваем, чтобы
# Proxmox проверил актуальность образа.
existing_volid = f'{storage}:{content}/{dl_filename}'
if client.volume_exists(node, storage, existing_volid):
_update_deploy_task(task_id, 'completed', f'Образ {dl_filename} уже в хранилище', 100, node=node)
logger.info(f"[IMG DL #{task_id}] {dl_filename} already present on {node}:{storage}")
return
if checksum:
logger.info(f"[IMG DL #{task_id}] Removing cached {existing_volid} to re-verify checksum")
client.delete_volume(node, storage, existing_volid)
else:
_update_deploy_task(task_id, 'completed', f'Образ {dl_filename} уже в хранилище', 100, node=node)
logger.info(f"[IMG DL #{task_id}] {dl_filename} already present on {node}:{storage}")
return
_update_deploy_task(task_id, 'running', f'Старт загрузки {dl_filename}...', 15, node=node)
try:
@@ -1057,10 +1063,19 @@ def _do_image_template_sync(task_id: int, server_id: int, node: str,
filename = _normalize_import_filename(filename)
import_volid = f'{import_storage}:import/{filename}'
need_download = True
if client.volume_exists(node, import_storage, import_volid):
# Образ уже скачан прошлой попыткой — пропускаем загрузку (идемпотентность)
_update_deploy_task(task_id, 'running', 'Образ уже загружен, пропуск...', 40, node=node)
else:
if checksum:
# Есть кэшированный файл, но задана контрольная сумма — удаляем
# и перекачиваем: Proxmox проверит сумму при новой загрузке.
logger.info(f"[IMG TPL #{task_id}] Removing cached {import_volid} to re-verify checksum")
client.delete_volume(node, import_storage, import_volid)
else:
# Без контрольной суммы доверяем кэшу (чистая идемпотентность)
need_download = False
_update_deploy_task(task_id, 'running', 'Образ уже загружен, пропуск...', 40, node=node)
if need_download:
_update_deploy_task(task_id, 'running', f'Загрузка образа {filename}...', 10, node=node)
try:
dl_upid = client.download_url(node, import_storage, url, 'import', filename,

View File

@@ -170,6 +170,21 @@ class StorageMixin:
logger.debug(f"volume_exists check failed {storage}/{volid}: {e}")
return False
def delete_volume(self, node: str, storage: str, volid: str) -> bool:
"""Удалить том из хранилища. volid вида 'storage:content/filename'.
Возвращает True при успехе, False при ошибке."""
if not self.proxmox:
return False
# proxmoxer принимает volume без префикса хранилища
volume = volid.split(':', 1)[1] if ':' in volid else volid
try:
self.proxmox.nodes(node).storage(storage).content(volume).delete()
logger.info(f"Deleted volume {volid} from {node}:{storage}")
return True
except Exception as e:
logger.warning(f"delete_volume failed {node}:{storage}/{volume}: {e}")
return False
def get_download_target_storages(self, node: str, content: str) -> List[Dict]:
"""
Получить активные хранилища ноды, поддерживающие указанный тип контента