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) <noreply@anthropic.com>
This commit is contained in:
duffyduck
2026-07-31 02:07:43 +02:00
co-authored by Claude Opus 5
parent e9aeaf9e62
commit e26d966a8b
+45 -3
View File
@@ -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)