diff --git a/pvesnap/proxmox.py b/pvesnap/proxmox.py index 1bb4060..14300bd 100644 --- a/pvesnap/proxmox.py +++ b/pvesnap/proxmox.py @@ -8,6 +8,7 @@ from __future__ import annotations import json import logging +import re import shutil import subprocess import time @@ -17,6 +18,9 @@ log = logging.getLogger("pvesnap.proxmox") PVESH = "/usr/bin/pvesh" +# UPID:node:PID:PSTART:STARTTIME:TYPE:ID:USER: +_UPID_PATTERN = re.compile(r"UPID:[^\s\"']+") + class ProxmoxError(Exception): """Ein pvesh-Aufruf ist fehlgeschlagen.""" @@ -184,7 +188,12 @@ class Proxmox: except ProxmoxError: return False - return self._retry(what, lambda: self._task(guest, args, what), skip_retry=exists) + def attempt(): + result = self._task(guest, args, what) + self._verify_created(guest, name) + return result + + return self._retry(what, attempt, skip_retry=exists) def delete_snapshot(self, guest, name, force=False): if self.dry_run: @@ -319,8 +328,35 @@ class Proxmox: @staticmethod def _extract_upid(output): - for line in (output or "").splitlines(): - candidate = line.strip().strip('"') - if candidate.startswith("UPID:"): - return candidate - return None + """UPID aus der Antwort von pvesh fischen. + + Bei Containern schreibt pvesh den Fortschritt vor die UPID, alles in + einer Zeile: + + Creating snap: 100% complete...done."UPID:node:...:vzsnapshot:100:..." + + Zeilenweise auf den Anfang zu pruefen findet die UPID dort nicht - und + ohne UPID bliebe ein fehlgeschlagener Task unbemerkt. + """ + match = _UPID_PATTERN.search(output or "") + return match.group(0) if match else None + + def _verify_created(self, guest, name): + """Nachsehen, ob der Snapshot wirklich brauchbar entstanden ist. + + Zweite Sicherung fuer den Fall, dass ein Fehlschlag weder ueber den + Rueckgabewert noch ueber den Task sichtbar wird: Proxmox laesst dann + einen halbfertigen Eintrag ("snapstate") in der Konfiguration stehen. + """ + try: + snapshots = self.list_snapshots(guest) + except ProxmoxError: + return # nicht schlimmer machen als noetig + found = next((s for s in snapshots if s.name == name), None) + if found is None: + raise ProxmoxError("Der Snapshot '%s' ist nach dem Anlegen nicht " + "vorhanden." % name) + if not found.complete: + raise ProxmoxError("Der Snapshot '%s' ist unvollstaendig geblieben " + "(snapstate: %s) - er enthaelt keine Daten." + % (name, found.snapstate))