From e26d966a8ba2ec0c997a2e0494066be1859b2315 Mon Sep 17 00:00:00 2001 From: duffyduck Date: Fri, 31 Jul 2026 02:07:43 +0200 Subject: [PATCH] Bei fehlgeschlagenen Tasks das Proxmox-Task-Log mitmelden Bisher stand im Fehlerfall nur der Exit-Status da ("cfs-lock ... error: got lock request timeout"), nicht aber, was Proxmox davor protokolliert hat. Fuer die Fehlersuche fehlte damit genau der interessante Teil. * Schlaegt ein Task fehl, werden die letzten Zeilen des Task-Logs an die Meldung angehaengt; identische Wiederholungen werden zusammengefasst ("trying to acquire cfs lock 'storage-data' ... (9x)"). * Die Meldung wiederholt nicht mehr VM und Snapshot-Namen, die der Aufrufer ohnehin voranstellt. Ausserdem ein Fehler in der Wiederholung: schlug der Task fehl, blieb der Snapshot-Eintrag teils in der VM-Konfiguration stehen. Der zweite Versuch lief dann in "snapshot already exists" - und diese Meldung verdeckte die eigentliche Ursache. Ist der Snapshot bereits vorhanden, wird jetzt nicht wiederholt und der urspruengliche Fehler gemeldet. Co-Authored-By: Claude Opus 5 (1M context) --- pvesnap/proxmox.py | 48 +++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 45 insertions(+), 3 deletions(-) diff --git a/pvesnap/proxmox.py b/pvesnap/proxmox.py index f5fb89d..84d51cd 100644 --- a/pvesnap/proxmox.py +++ b/pvesnap/proxmox.py @@ -169,7 +169,14 @@ class Proxmox: log.info("[TESTLAUF] wuerde Snapshot anlegen: %s -> %s", guest.label, name) return None what = "Snapshot %s fuer %s" % (name, guest.label) - return self._retry(what, lambda: self._task(guest, args, what)) + + def exists(): + try: + return any(snap.name == name for snap in self.list_snapshots(guest)) + except ProxmoxError: + return False + + return self._retry(what, lambda: self._task(guest, args, what), skip_retry=exists) def delete_snapshot(self, guest, name): if self.dry_run: @@ -204,7 +211,7 @@ class Proxmox: text = str(message).lower() return any(marker in text for marker in cls._TRANSIENT) - def _retry(self, what, action): + def _retry(self, what, action, skip_retry=None): attempt = 0 while True: try: @@ -212,6 +219,13 @@ class Proxmox: except ProxmoxError as exc: if attempt >= self.retries or not self._is_transient(exc): raise + if skip_retry is not None and skip_retry(): + # Der Snapshot ist trotz Fehler schon da - ein zweiter + # Versuch scheiterte nur an "already exists" und wuerde + # die eigentliche Ursache verdecken. + log.warning("%s: Snapshot ist trotz Fehler vorhanden - " + "keine Wiederholung", what) + raise attempt += 1 log.warning("%s: %s - Versuch %d von %d in %ds", what, exc, attempt + 1, self.retries + 1, self.retry_delay) @@ -238,7 +252,9 @@ class Proxmox: if status.get("status") == "stopped": exit_status = status.get("exitstatus") or "unbekannt" if exit_status != "OK": - raise ProxmoxError("%s fehlgeschlagen: %s" % (what, exit_status)) + # Nur die Ursache melden - wer und was, ergaenzt der Aufrufer. + raise ProxmoxError("%s%s" % (exit_status, + self._task_log_tail(guest.node, upid))) return upid if time.time() > deadline: raise ProxmoxError("%s: Zeitueberschreitung nach %ds (Task laeuft weiter: %s)" @@ -246,6 +262,32 @@ class Proxmox: time.sleep(delay) delay = min(delay * 1.5, 5.0) + def _task_log_tail(self, node, upid, lines=4): + """Die letzten Zeilen des Task-Logs - dort steht, woran es lag. + + Wiederholungen wie "trying to acquire cfs lock ..." werden + zusammengefasst, sonst besteht die Meldung nur noch daraus. + """ + try: + entries = self._json(["get", "/nodes/%s/tasks/%s/log" % (node, upid), + "--limit", "100"]) or [] + except ProxmoxError: + return "" + + texts = [] + for entry in entries: + text = str(entry.get("t") or "").strip() + if not text: + continue + if texts and texts[-1][0] == text: + texts[-1][1] += 1 + else: + texts.append([text, 1]) + + tail = ["%s%s" % (text, " (%dx)" % count if count > 1 else "") + for text, count in texts[-lines:]] + return " [Task-Log: %s]" % " | ".join(tail) if tail else "" + def _wait_unlocked(self, guest, timeout=None): """Wartet, bis der Gast keine laufende Sperre mehr hat.""" deadline = time.time() + (timeout or self.task_timeout)