Explorer: Transfer-Laufwerke auf F2; cleanup findet verwaiste Snapshot-Sperren
Zwei Luecken, die seit dem Umbau offenstanden.
1. Transfer-Laufwerke waren aus dem Explorer nicht erreichbar
F2 zeigte nur die Dateisysteme des Snapshots. Jetzt stehen die
Transfer-Laufwerke in derselben Liste - aus Sicht des Bedieners ist es
dieselbe Frage ("wo soll ich hinschauen?"), und der Weg ueber
pvesnap-recovery entfaellt.
Das gewaehlte Laufwerk landet im rechten Fenster, links bleibt der
Snapshot. Damit laesst sich direkt aus einem Snapshot auf das
Austauschmedium kopieren, das anschliessend in die wiederhergestellte
Maschine wandert - ohne Zwischenlager auf dem Host.
Ausgehaengt wird beim Verlassen, und zwar nur, was wir selbst eingehaengt
haben: ein Laufwerk, das schon vorher am Host hing, gehoert jemand
anderem. Das finally faengt auch Absturz und Strg-C ab - bliebe es
eingehaengt, gaelte es spaeter als belegt.
2. cleanup fand geschuetzte Snapshots ohne Klon nicht
Loescht jemand eine Wiederherstellung in der Proxmox-Oberflaeche, raeumt
Proxmox den Klon durchaus mit ab - aber nicht den Schutz seines
Quell-Snapshots (rbd snap protect, den setzt es beim Klonen selbst).
Zurueck bleibt ein geschuetzter Snapshot ohne Klon: die Vorhaltezeit
scheitert an ihm jede Nacht mit "snapshot is protected", und
find_orphans() findet prinzipiell nichts, weil es den Datentraeger, nach
dem es sucht, wirklich nicht mehr gibt. Dasselbe entsteht nach einem von
Hand ausgefuehrten rbd flatten.
find_stale_protections() sucht deshalb direkt nach der Ursache: jeder
geschuetzte Snapshot, an dem kein Klon haengt. Ein solcher Schutz hat
keinen Zweck - er existiert einzig dafuer, dass ein Klon seine Grundlage
behaelt. Geloest wird nur der Schutz, geloescht wird nichts. Vor jedem
Loesen wird ein zweites Mal nachgesehen, ob inzwischen doch ein Klon
daran haengt.
cleanup laeuft damit in zwei Durchgaengen und liegt neu auch in der
Oberflaeche auf Taste c - man sucht sonst lange nach einem Befehl, den
man nur im Notfall braucht.
Getestet gegen die Kulisse der Handbuch-Werkstatt: die rbd-Attrappe
kennt jetzt snap ls und children und enthaelt beide Zustaende
nebeneinander - ein geschuetzter Snapshot mit Klon (muss in Ruhe
gelassen werden) und einer ohne (muss gefunden werden).
Handbuch und README nachgezogen, zwei Bildschirmfotos dazu.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
59e7224297
commit
d9d9345d03
+113
-12
@@ -1535,18 +1535,7 @@ def find_orphans(proxmox):
|
||||
"stimmen. Zur Sicherheit wird nichts angefasst." % CONF_ROOT)
|
||||
|
||||
found = []
|
||||
for storage in proxmox._json(["get", "/storage"]) or []:
|
||||
if storage.get("type") != "rbd":
|
||||
continue
|
||||
name = storage.get("storage")
|
||||
context = _rbd_context(proxmox, "%s:dummy" % name)
|
||||
if context is None:
|
||||
continue
|
||||
args, _ = context
|
||||
pool = storage.get("pool") or "rbd"
|
||||
namespace = storage.get("namespace")
|
||||
prefix = "%s/%s" % (pool, (namespace + "/") if namespace else "")
|
||||
|
||||
for name, args, pool, prefix in _rbd_storages(proxmox):
|
||||
for image in run(args + ["ls", pool.rstrip("/")], check=False,
|
||||
timeout=120).splitlines():
|
||||
image = image.strip()
|
||||
@@ -1605,6 +1594,118 @@ def remove_orphans(proxmox, orphans, progress=None):
|
||||
return removed, failed
|
||||
|
||||
|
||||
def _is_protected(entry):
|
||||
"""rbd meldet das je nach Version als true oder als "true"."""
|
||||
value = entry.get("protected")
|
||||
return value is True or str(value).strip().lower() == "true"
|
||||
|
||||
|
||||
def _rbd_storages(proxmox):
|
||||
"""[(Name, rbd-Basisbefehl, Praefix pool/[namespace/])] aller Ceph-Storages."""
|
||||
result = []
|
||||
for storage in proxmox._json(["get", "/storage"]) or []:
|
||||
if storage.get("type") != "rbd":
|
||||
continue
|
||||
name = storage.get("storage")
|
||||
context = _rbd_context(proxmox, "%s:dummy" % name)
|
||||
if context is None:
|
||||
continue
|
||||
args, _ = context
|
||||
pool = storage.get("pool") or "rbd"
|
||||
namespace = storage.get("namespace")
|
||||
result.append((name, args, pool,
|
||||
"%s/%s" % (pool, (namespace + "/") if namespace else "")))
|
||||
return result
|
||||
|
||||
|
||||
def find_stale_protections(proxmox, progress=None):
|
||||
"""Geschuetzte Snapshots, an denen kein Klon mehr haengt.
|
||||
|
||||
Der Fall, den find_orphans() nicht findet: Wird eine Wiederherstellung in
|
||||
der Proxmox-Oberflaeche geloescht, raeumt Proxmox den Klon durchaus mit ab -
|
||||
aber nicht den Schutz seines Quell-Snapshots. Den setzt es beim Klonen
|
||||
selbst (`rbd snap protect`, RBD verlangt ihn), und niemand nimmt ihn wieder
|
||||
weg. Zurueck bleibt ein geschuetzter Snapshot ohne Klon: Die Vorhaltezeit
|
||||
scheitert an ihm fuer immer mit "snapshot is protected", und nach einem
|
||||
liegengebliebenen Datentraeger sucht man vergeblich - der ist ja wirklich
|
||||
weg.
|
||||
|
||||
Dasselbe entsteht nach einem von Hand ausgefuehrten `rbd flatten`: der Klon
|
||||
haengt danach an nichts mehr, der Schutz steht aber weiter.
|
||||
|
||||
Ein geschuetzter Snapshot ohne Kinder hat keinen Zweck - der Schutz
|
||||
existiert einzig, damit ein Klon seine Grundlage behaelt.
|
||||
"""
|
||||
def step(text):
|
||||
if progress:
|
||||
progress(text)
|
||||
|
||||
found = []
|
||||
for name, args, pool, prefix in _rbd_storages(proxmox):
|
||||
images = [line.strip() for line
|
||||
in run(args + ["ls", pool.rstrip("/")], check=False,
|
||||
timeout=120).splitlines() if line.strip()]
|
||||
step("%s: %d Datentraeger" % (name, len(images)))
|
||||
for image in images:
|
||||
try:
|
||||
snapshots = json.loads(
|
||||
run(args + ["snap", "ls", prefix + image, "--format", "json"],
|
||||
check=False, timeout=60, quiet=True) or "[]")
|
||||
except ValueError:
|
||||
continue
|
||||
if not isinstance(snapshots, list):
|
||||
continue
|
||||
for snapshot in snapshots:
|
||||
if not _is_protected(snapshot):
|
||||
continue
|
||||
snapname = str(snapshot.get("name") or "")
|
||||
if not snapname:
|
||||
continue
|
||||
target = "%s%s@%s" % (prefix, image, snapname)
|
||||
if run(args + ["children", target], check=False,
|
||||
timeout=120, quiet=True).strip():
|
||||
continue # da haengt noch etwas dran - gut so
|
||||
found.append({
|
||||
"volid": "%s:%s" % (name, image),
|
||||
"snapshot": snapname,
|
||||
"image": prefix + image,
|
||||
"bytes": int(snapshot.get("size") or 0),
|
||||
"args": args,
|
||||
})
|
||||
return found
|
||||
|
||||
|
||||
def release_protections(proxmox, entries, progress=None):
|
||||
"""Den Schutz der gefundenen Snapshots loesen.
|
||||
|
||||
Loescht nichts - danach darf die Vorhaltezeit den Snapshot wieder
|
||||
wegraeumen, mehr passiert nicht.
|
||||
"""
|
||||
def step(text):
|
||||
log.info("%s", text)
|
||||
if progress:
|
||||
progress(text)
|
||||
|
||||
released, failed = [], []
|
||||
for entry in entries:
|
||||
target = "%s@%s" % (entry["image"], entry["snapshot"])
|
||||
# Zwischen Suchen und Freigeben kann jemand einen Klon angelegt haben.
|
||||
# Ein zweiter Blick kostet nichts und verhindert, dass wir einem
|
||||
# frischen Klon die Grundlage unter den Fuessen wegziehen.
|
||||
if run(entry["args"] + ["children", target], check=False,
|
||||
timeout=120, quiet=True).strip():
|
||||
failed.append("%s: haengt inzwischen wieder ein Klon daran" % target)
|
||||
continue
|
||||
step("Hebe Schutz von %s auf" % target)
|
||||
try:
|
||||
run(entry["args"] + ["snap", "unprotect", entry["image"],
|
||||
"--snap", entry["snapshot"]], timeout=120)
|
||||
released.append(target)
|
||||
except RecoveryError as exc:
|
||||
failed.append("%s: %s" % (target, exc))
|
||||
return released, failed
|
||||
|
||||
|
||||
def destroy(proxmox, instance, progress=None, keep_snapshot_protection=False):
|
||||
"""Eine Wiederherstellung restlos entfernen."""
|
||||
def step(text):
|
||||
|
||||
Reference in New Issue
Block a user