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>
@@ -424,9 +424,17 @@ Links der Snapshot, rechts der lokale Rechner:
|
|||||||
| `F3` / `v` | Datei ansehen (Text oder Hex) |
|
| `F3` / `v` | Datei ansehen (Text oder Hex) |
|
||||||
| `F7` / `n` | neues Verzeichnis (nur lokal) |
|
| `F7` / `n` | neues Verzeichnis (nur lokal) |
|
||||||
| `F6` / `g` | Verzeichnis direkt eingeben |
|
| `F6` / `g` | Verzeichnis direkt eingeben |
|
||||||
| `F2` / `m` | anderes Dateisystem des Snapshots wählen |
|
| `F2` / `m` | Laufwerk wählen: anderes Dateisystem des Snapshots — oder ein Transfer-Laufwerk ins rechte Fenster |
|
||||||
| `F10` / `q` | eine Ebene zurück (Snapshot wird ausgehängt) |
|
| `F10` / `q` | eine Ebene zurück (Snapshot wird ausgehängt) |
|
||||||
|
|
||||||
|
**`F2` holt auch Transfer-Laufwerke herein.** In derselben Liste stehen unter
|
||||||
|
den Dateisystemen des Snapshots die
|
||||||
|
[Transfer-Laufwerke](#daten-hinein-und-wieder-heraus--transfer-laufwerke); das
|
||||||
|
gewählte wird am Host eingehängt und im **rechten** Fenster geöffnet. Damit
|
||||||
|
lässt sich direkt aus einem Snapshot auf das Austauschmedium kopieren, das
|
||||||
|
anschließend in die wiederhergestellte Maschine wandert. Beim Verlassen wird es
|
||||||
|
wieder ausgehängt — sonst gälte es später als belegt.
|
||||||
|
|
||||||
Der Explorer hat drei Ebenen: **Gastauswahl → Snapshot-Auswahl → Dateien**. Mit
|
Der Explorer hat drei Ebenen: **Gastauswahl → Snapshot-Auswahl → Dateien**. Mit
|
||||||
`q` geht es jeweils eine Ebene zurück; beim Verlassen des Dateibrowsers wird der
|
`q` geht es jeweils eine Ebene zurück; beim Verlassen des Dateibrowsers wird der
|
||||||
Snapshot wieder ausgehängt, sodass man gleich den nächsten öffnen kann. Das
|
Snapshot wieder ausgehängt, sodass man gleich den nächsten öffnen kann. Das
|
||||||
@@ -720,14 +728,31 @@ pvesnap-recovery cleanup # zeigt Gefundenes, fragt, räumt auf
|
|||||||
pvesnap-recovery cleanup --all # auch Datenträger ohne Elternteil
|
pvesnap-recovery cleanup --all # auch Datenträger ohne Elternteil
|
||||||
```
|
```
|
||||||
|
|
||||||
Gesucht wird nach Datenträgern der Form `vm-<id>-disk-N`, zu deren VMID es
|
In der Oberfläche liegt das auf Taste `c`. Aufgeräumt wird in **zwei
|
||||||
**keine Konfigurationsdatei** unter `/etc/pve/nodes/*/` mehr gibt. Bewusst
|
Durchgängen**, weil zwei verschiedene Dinge liegenbleiben können.
|
||||||
|
|
||||||
|
**1. Datenträger ohne Gast.** Gesucht wird nach Datenträgern der Form
|
||||||
|
`vm-<id>-disk-N`, zu deren VMID es **keine Konfigurationsdatei** unter
|
||||||
|
`/etc/pve/nodes/*/` mehr gibt. Bewusst
|
||||||
nicht über `/cluster/resources`: Ein Gast auf einem abgemeldeten Node taucht
|
nicht über `/cluster/resources`: Ein Gast auf einem abgemeldeten Node taucht
|
||||||
dort unter Umständen nicht auf — und dann würden die Platten einer lebenden
|
dort unter Umständen nicht auf — und dann würden die Platten einer lebenden
|
||||||
Maschine als verwaist gelten. Ohne Elternteil wird nichts von selbst entfernt,
|
Maschine als verwaist gelten. Ohne Elternteil wird nichts von selbst entfernt,
|
||||||
und der Schutz eines Snapshots wird nur gelöst, wenn wirklich kein Klon mehr
|
und der Schutz eines Snapshots wird nur gelöst, wenn wirklich kein Klon mehr
|
||||||
daran hängt.
|
daran hängt.
|
||||||
|
|
||||||
|
**2. Geschützte Snapshots ohne Klon.** Den Fall findet der erste Durchgang
|
||||||
|
prinzipiell nicht: Proxmox räumt beim Löschen in der Weboberfläche den Klon
|
||||||
|
durchaus mit ab — nur den Schutz des Quell-Snapshots nicht. Zurück bleibt ein
|
||||||
|
geschützter Snapshot ohne Klon, an dem die Vorhaltezeit jede Nacht scheitert,
|
||||||
|
während es keinen liegengebliebenen Datenträger gibt, nach dem man suchen
|
||||||
|
könnte. Dasselbe entsteht nach einem von Hand ausgeführten `rbd flatten`.
|
||||||
|
|
||||||
|
Gesucht wird deshalb direkt nach der Ursache: jeder geschützte Snapshot, an dem
|
||||||
|
kein Klon hängt. Ein solcher Schutz hat keinen Zweck — er existiert einzig
|
||||||
|
dafür, dass ein Klon seine Grundlage behält. **Gelöst wird nur der Schutz,
|
||||||
|
gelöscht wird nichts**; vor jedem Lösen wird ein zweites Mal nachgesehen, ob
|
||||||
|
inzwischen doch ein Klon daran hängt.
|
||||||
|
|
||||||
### Und wenn die Maschine nicht auf Ceph liegt?
|
### Und wenn die Maschine nicht auf Ceph liegt?
|
||||||
|
|
||||||
Das ganze Thema „Abhängigkeit vom Quell-Snapshot" ist eine **Ceph-Eigenheit**.
|
Das ganze Thema „Abhängigkeit vom Quell-Snapshot" ist eine **Ceph-Eigenheit**.
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<text x="31.2" y="165.3" fill="#d3d7cf" font-weight="bold" textLength="980.4" lengthAdjust="spacing" xml:space="preserve">-- Zeitplan ------------------------------------------------------------------------------------------------------</text>
|
<text x="31.2" y="165.3" fill="#d3d7cf" font-weight="bold" textLength="980.4" lengthAdjust="spacing" xml:space="preserve">-- Zeitplan ------------------------------------------------------------------------------------------------------</text>
|
||||||
<text x="14.0" y="183.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Art des Zeitplans taeglich </text>
|
<text x="14.0" y="183.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Art des Zeitplans taeglich </text>
|
||||||
<text x="14.0" y="201.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Uhrzeit (HH:MM) 02:30 </text>
|
<text x="14.0" y="201.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Uhrzeit (HH:MM) 02:30 </text>
|
||||||
<text x="14.0" y="219.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Naechster Termin (Vorschau) Mo 10.08.2026 02:30 (taeglich um 02:30) </text>
|
<text x="14.0" y="219.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Naechster Termin (Vorschau) Mi 12.08.2026 02:30 (taeglich um 02:30) </text>
|
||||||
<text x="31.2" y="237.3" fill="#d3d7cf" font-weight="bold" textLength="980.4" lengthAdjust="spacing" xml:space="preserve">-- Vorhaltezeit --------------------------------------------------------------------------------------------------</text>
|
<text x="31.2" y="237.3" fill="#d3d7cf" font-weight="bold" textLength="980.4" lengthAdjust="spacing" xml:space="preserve">-- Vorhaltezeit --------------------------------------------------------------------------------------------------</text>
|
||||||
<text x="14.0" y="255.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Anzahl behalten (je VM) 14 </text>
|
<text x="14.0" y="255.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Anzahl behalten (je VM) 14 </text>
|
||||||
<text x="14.0" y="273.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Maximales Alter 3w </text>
|
<text x="14.0" y="273.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Maximales Alter 3w </text>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 7.4 KiB After Width: | Height: | Size: 7.4 KiB |
@@ -15,17 +15,21 @@
|
|||||||
<text x="14.0" y="183.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> F3 / v Datei ansehen (Text oder Hex) </text>
|
<text x="14.0" y="183.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> F3 / v Datei ansehen (Text oder Hex) </text>
|
||||||
<text x="14.0" y="201.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> F7 / n neues Verzeichnis (nur im lokalen Fenster) </text>
|
<text x="14.0" y="201.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> F7 / n neues Verzeichnis (nur im lokalen Fenster) </text>
|
||||||
<text x="14.0" y="219.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> F6 / g Verzeichnis direkt eingeben </text>
|
<text x="14.0" y="219.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> F6 / g Verzeichnis direkt eingeben </text>
|
||||||
<text x="14.0" y="237.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> F2 / m anderes Dateisystem des Snapshots waehlen </text>
|
<text x="14.0" y="237.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> F2 / m Laufwerk waehlen: anderes Dateisystem des Snapshots </text>
|
||||||
<text x="14.0" y="255.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> r neu einlesen </text>
|
<text x="14.0" y="255.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> oder ein Transfer-Laufwerk ins rechte Fenster </text>
|
||||||
<text x="14.0" y="273.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> F10 / q zurueck zur Snapshot-Auswahl </text>
|
<text x="14.0" y="273.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> r neu einlesen </text>
|
||||||
<text x="14.0" y="309.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Der Snapshot ist schreibgeschuetzt eingebunden - es kann nichts </text>
|
<text x="14.0" y="291.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> F10 / q zurueck zur Snapshot-Auswahl </text>
|
||||||
<text x="14.0" y="327.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> kaputtgehen. Kopiert wird immer vom aktiven ins andere Fenster. </text>
|
<text x="14.0" y="327.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Der Snapshot ist schreibgeschuetzt eingebunden - es kann nichts </text>
|
||||||
<text x="14.0" y="345.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Geraetedateien und Sockets werden uebersprungen, symbolische </text>
|
<text x="14.0" y="345.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> kaputtgehen. Kopiert wird immer vom aktiven ins andere Fenster. </text>
|
||||||
<text x="14.0" y="363.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Verweise bleiben Verweise. </text>
|
<text x="14.0" y="363.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Geraetedateien und Sockets werden uebersprungen, symbolische </text>
|
||||||
<text x="14.0" y="399.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Es gibt drei Ebenen: Gastauswahl -> Snapshot-Auswahl -> Dateien. </text>
|
<text x="14.0" y="381.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Verweise bleiben Verweise. </text>
|
||||||
<text x="14.0" y="417.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> F10 oder q geht jeweils eine Ebene zurueck; der Snapshot wird </text>
|
<text x="14.0" y="417.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Transfer-Laufwerke (F2) sind Austauschmedien fuer Maschinen ohne </text>
|
||||||
<text x="14.0" y="435.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> dabei wieder ausgehaengt. Das rechte Fenster behaelt sein </text>
|
<text x="14.0" y="435.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Netz: hier befuellen, spaeter in die Wiederherstellung haengen. </text>
|
||||||
<text x="14.0" y="453.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Verzeichnis, auch wenn man den naechsten Snapshot oeffnet. </text>
|
<text x="14.0" y="453.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Beim Verlassen werden sie wieder ausgehaengt. </text>
|
||||||
|
<text x="14.0" y="489.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Es gibt drei Ebenen: Gastauswahl -> Snapshot-Auswahl -> Dateien. </text>
|
||||||
|
<text x="14.0" y="507.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> F10 oder q geht jeweils eine Ebene zurueck; der Snapshot wird </text>
|
||||||
|
<text x="14.0" y="525.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> dabei wieder ausgehaengt. Das rechte Fenster behaelt sein </text>
|
||||||
|
<text x="14.0" y="543.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> Verzeichnis, auch wenn man den naechsten Snapshot oeffnet. </text>
|
||||||
<rect x="14.0" y="602.0" width="1006.6" height="18.4" fill="#d3d7cf"/>
|
<rect x="14.0" y="602.0" width="1006.6" height="18.4" fill="#d3d7cf"/>
|
||||||
<text x="14.0" y="615.3" fill="#2e3436" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> beliebige Taste = zurueck </text>
|
<text x="14.0" y="615.3" fill="#2e3436" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> beliebige Taste = zurueck </text>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 6.4 KiB |
@@ -0,0 +1,59 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1042.8 634.0" width="1043" height="634" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, 'DejaVu Sans Mono', monospace" font-size="14.5">
|
||||||
|
<rect width="1042.8" height="634.0" rx="7" fill="#1b1e24"/>
|
||||||
|
<path d="M0 7a7 7 0 0 1 7-7h1028.8a7 7 0 0 1 7 7v23.0H0z" fill="#2b2f38"/>
|
||||||
|
<circle cx="16.0" cy="15" r="5" fill="#ed6a5e"/>
|
||||||
|
<circle cx="33.0" cy="15" r="5" fill="#f4bf4f"/>
|
||||||
|
<circle cx="50.0" cy="15" r="5" fill="#61c554"/>
|
||||||
|
<text x="521.4" y="20" fill="#9aa3b2" font-size="12" text-anchor="middle">root@pve1 — pvesnap-explorer 101</text>
|
||||||
|
<rect x="14.0" y="44.0" width="1006.6" height="18.4" fill="#3465a4"/>
|
||||||
|
<text x="14.0" y="57.3" fill="#eeeeec" font-weight="bold" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> pvesnap-explorer - VM 101 (db01) - Snapshot auto-taeglich-20260809-023000 </text>
|
||||||
|
<rect x="14.0" y="62.0" width="507.8" height="18.4" fill="#06989a"/>
|
||||||
|
<text x="14.0" y="75.3" fill="#2e3436" textLength="507.4" lengthAdjust="spacing" xml:space="preserve"> VM 101 (db01) @ auto-taeglich-20260809-023000 </text>
|
||||||
|
<text x="521.4" y="75.3" fill="#d3d7cf" textLength="507.4" lengthAdjust="spacing" xml:space="preserve">│ Lokaler Rechner </text>
|
||||||
|
<text x="14.0" y="93.3" fill="#d3d7cf" font-weight="bold" textLength="507.4" lengthAdjust="spacing" xml:space="preserve">/run/pvesnap/mnt/101-auto-taeglich-20260809-023000 </text>
|
||||||
|
<text x="521.4" y="93.3" fill="#d3d7cf" textLength="507.4" lengthAdjust="spacing" xml:space="preserve">│/root </text>
|
||||||
|
<rect x="14.0" y="98.0" width="507.8" height="18.4" fill="#06989a"/>
|
||||||
|
<text x="14.0" y="111.3" fill="#2e3436" textLength="507.4" lengthAdjust="spacing" xml:space="preserve"> etc <DIR> 06.12.24 11:42</text>
|
||||||
|
<text x="521.4" y="111.3" fill="#d3d7cf" textLength="8.6" lengthAdjust="spacing" xml:space="preserve">│</text>
|
||||||
|
<text x="530.0" y="111.3" fill="#06989a" textLength="490.2" lengthAdjust="spacing" xml:space="preserve"> .. <hoch> </text>
|
||||||
|
<text x="14.0" y="129.3" fill="#06989a" textLength="507.4" lengthAdjust="spacing" xml:space="preserve"> home <DIR> 06.12.24 11:42</text>
|
||||||
|
<text x="521.4" y="129.3" fill="#d3d7cf" textLength="8.6" lengthAdjust="spacing" xml:space="preserve">│</text>
|
||||||
|
<text x="530.0" y="129.3" fill="#06989a" textLength="490.2" lengthAdjust="spacing" xml:space="preserve"> holen <DIR> 06.08.26 11:42</text>
|
||||||
|
<text x="14.0" y="147.3" fill="#06989a" textLength="507.4" lengthAdjust="spacing" xml:space="preserve"> root <DIR> 06.12.24 11:42</text>
|
||||||
|
<text x="521.4" y="147.3" fill="#d3d7cf" textLength="8.6" lengthAdjust="spacing" xml:space="preserve">│</text>
|
||||||
|
<text x="530.0" y="147.3" fill="#06989a" textLength="490.2" lengthAdjust="spacing" xml:space="preserve"> werkzeuge <DIR> 06.08.26 11:42</text>
|
||||||
|
<text x="14.0" y="165.3" fill="#06989a" textLength="507.4" lengthAdjust="spacing" xml:space="preserve"> srv <DIR> 06.12.24 11:42</text>
|
||||||
|
<text x="521.4" y="165.3" fill="#d3d7cf" textLength="507.4" lengthAdjust="spacing" xml:space="preserve">│ notizen.md 3.1K 06.08.26 11:42 </text>
|
||||||
|
<text x="14.0" y="183.3" fill="#06989a" textLength="507.4" lengthAdjust="spacing" xml:space="preserve"> var <DIR> 06.12.24 11:42</text>
|
||||||
|
<text x="521.4" y="183.3" fill="#d3d7cf" textLength="507.4" lengthAdjust="spacing" xml:space="preserve">│ </text>
|
||||||
|
<text x="14.0" y="201.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ </text>
|
||||||
|
<text x="14.0" y="219.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ </text>
|
||||||
|
<text x="14.0" y="237.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ </text>
|
||||||
|
<text x="14.0" y="255.3" fill="#d3d7cf" textLength="103.2" lengthAdjust="spacing" xml:space="preserve"> ┌─</text>
|
||||||
|
<text x="117.2" y="255.3" fill="#d3d7cf" font-weight="bold" textLength="163.4" lengthAdjust="spacing" xml:space="preserve"> Welches Laufwerk? </text>
|
||||||
|
<text x="280.6" y="255.3" fill="#d3d7cf" textLength="748.2" lengthAdjust="spacing" xml:space="preserve">───────────────────────────────────────────────────────────────────────────┐ </text>
|
||||||
|
<text x="14.0" y="273.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ │ </text>
|
||||||
|
<text x="14.0" y="291.3" fill="#d3d7cf" textLength="103.2" lengthAdjust="spacing" xml:space="preserve"> │ </text>
|
||||||
|
<rect x="117.2" y="278.0" width="800.2" height="18.4" fill="#06989a"/>
|
||||||
|
<text x="117.2" y="291.3" fill="#2e3436" textLength="799.8" lengthAdjust="spacing" xml:space="preserve"> Snapshot: / (ext4, 400G) - wahrscheinlich das System </text>
|
||||||
|
<text x="917.0" y="291.3" fill="#d3d7cf" textLength="111.8" lengthAdjust="spacing" xml:space="preserve"> │ </text>
|
||||||
|
<text x="14.0" y="309.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ Snapshot: /boot (ext4, 1G) │ </text>
|
||||||
|
<text x="14.0" y="327.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ Transfer: dumps 20.0G Datenbank-Dumps und Expor… [in Benutzung von 9101] │ </text>
|
||||||
|
<text x="14.0" y="345.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ Transfer: werkzeuge 4.0G Skripte, Treiber, Install… │ </text>
|
||||||
|
<text x="14.0" y="363.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ Transfer: austausch 8.0G Alles andere │ </text>
|
||||||
|
<text x="14.0" y="381.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ │ </text>
|
||||||
|
<text x="14.0" y="399.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> └───────────────────────────────────────────────────────────────────────────────────────────────┘ </text>
|
||||||
|
<text x="14.0" y="417.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ </text>
|
||||||
|
<text x="14.0" y="435.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ </text>
|
||||||
|
<text x="14.0" y="453.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ </text>
|
||||||
|
<text x="14.0" y="471.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ </text>
|
||||||
|
<text x="14.0" y="489.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ </text>
|
||||||
|
<text x="14.0" y="507.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ </text>
|
||||||
|
<text x="14.0" y="525.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> │ </text>
|
||||||
|
<text x="14.0" y="543.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> 5 Eintraege │ 4 Eintraege </text>
|
||||||
|
<text x="14.0" y="561.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> /run/pvesnap/mnt/101-auto-taeglich-20260809-023000/etc </text>
|
||||||
|
<rect x="14.0" y="584.0" width="1006.6" height="18.4" fill="#d3d7cf"/>
|
||||||
|
<text x="14.0" y="597.3" fill="#2e3436" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> Tab Fenster | Enter Oeffnen | Leer Markieren | F5 Kopieren </text>
|
||||||
|
<rect x="14.0" y="602.0" width="1006.6" height="18.4" fill="#d3d7cf"/>
|
||||||
|
<text x="14.0" y="615.3" fill="#2e3436" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> F3 Ansehen | F7 Neuer Ordner | F2 Laufwerk | F10 Zurueck </text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 10 KiB |
@@ -0,0 +1,16 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1042.8 634.0" width="1043" height="634" font-family="ui-monospace, SFMono-Regular, Menlo, Consolas, 'DejaVu Sans Mono', monospace" font-size="14.5">
|
||||||
|
<rect width="1042.8" height="634.0" rx="7" fill="#1b1e24"/>
|
||||||
|
<path d="M0 7a7 7 0 0 1 7-7h1028.8a7 7 0 0 1 7 7v23.0H0z" fill="#2b2f38"/>
|
||||||
|
<circle cx="16.0" cy="15" r="5" fill="#ed6a5e"/>
|
||||||
|
<circle cx="33.0" cy="15" r="5" fill="#f4bf4f"/>
|
||||||
|
<circle cx="50.0" cy="15" r="5" fill="#61c554"/>
|
||||||
|
<text x="521.4" y="20" fill="#9aa3b2" font-size="12" text-anchor="middle">root@pve1 — pvesnap-recovery</text>
|
||||||
|
<rect x="14.0" y="44.0" width="1006.6" height="18.4" fill="#3465a4"/>
|
||||||
|
<text x="14.0" y="57.3" fill="#eeeeec" font-weight="bold" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> Aufraeumen </text>
|
||||||
|
<text x="31.2" y="93.3" fill="#d3d7cf" font-weight="bold" textLength="464.4" lengthAdjust="spacing" xml:space="preserve">Geschuetzte Snapshots, an denen kein Klon mehr haengt:</text>
|
||||||
|
<text x="31.2" y="111.3" fill="#c4a000" textLength="464.4" lengthAdjust="spacing" xml:space="preserve"> ceph-vm:vm-105-disk-0@auto-taeglich-20260809-023000</text>
|
||||||
|
<text x="14.0" y="147.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> An diesen scheitert die Vorhaltezeit dauerhaft mit "snapshot is </text>
|
||||||
|
<text x="14.0" y="165.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> protected". Der Schutz wird geloest - geloescht wird dabei nichts. </text>
|
||||||
|
<rect x="14.0" y="602.0" width="1006.6" height="18.4" fill="#d3d7cf"/>
|
||||||
|
<text x="14.0" y="615.3" fill="#2e3436" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> j = aufraeumen | andere Taste = nichts tun </text>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 2.0 KiB |
@@ -12,5 +12,7 @@
|
|||||||
<text x="14.0" y="111.3" fill="#2e3436" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> VM 9101 running pve1 VM 101 @ auto-stuendlich-20… live </text>
|
<text x="14.0" y="111.3" fill="#2e3436" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> VM 9101 running pve1 VM 101 @ auto-stuendlich-20… live </text>
|
||||||
<text x="14.0" y="129.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> VM 9102 stopped pve2 VM 105 @ auto-taeglich-2026… recover </text>
|
<text x="14.0" y="129.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> VM 9102 stopped pve2 VM 105 @ auto-taeglich-2026… recover </text>
|
||||||
<rect x="14.0" y="584.0" width="1006.6" height="18.4" fill="#d3d7cf"/>
|
<rect x="14.0" y="584.0" width="1006.6" height="18.4" fill="#d3d7cf"/>
|
||||||
<text x="14.0" y="597.3" fill="#2e3436" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> Enter Details | n Neu | s Starten | h Herunterfahren | x Verwerfen | v Laufwerke | r Aktualisieren | q Beenden </text>
|
<text x="14.0" y="597.3" fill="#2e3436" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> Enter Details | n Neu | s Starten | h Herunterfahren | x Verwerfen </text>
|
||||||
|
<rect x="14.0" y="602.0" width="1006.6" height="18.4" fill="#d3d7cf"/>
|
||||||
|
<text x="14.0" y="615.3" fill="#2e3436" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> v Laufwerke | c Aufraeumen | r Aktualisieren | q Beenden </text>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -13,4 +13,6 @@
|
|||||||
<text x="14.0" y="129.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> VM 9102 stopped pve2 VM 105 @ auto-taeglich-2026… recover </text>
|
<text x="14.0" y="129.3" fill="#d3d7cf" textLength="1014.8" lengthAdjust="spacing" xml:space="preserve"> VM 9102 stopped pve2 VM 105 @ auto-taeglich-2026… recover </text>
|
||||||
<rect x="14.0" y="584.0" width="1006.6" height="18.4" fill="#cc0000"/>
|
<rect x="14.0" y="584.0" width="1006.6" height="18.4" fill="#cc0000"/>
|
||||||
<text x="14.0" y="597.3" fill="#eeeeec" font-weight="bold" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> VM 9101 wirklich verwerfen? Alle Aenderungen darin sind weg. [j/n] </text>
|
<text x="14.0" y="597.3" fill="#eeeeec" font-weight="bold" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> VM 9101 wirklich verwerfen? Alle Aenderungen darin sind weg. [j/n] </text>
|
||||||
|
<rect x="14.0" y="602.0" width="1006.6" height="18.4" fill="#d3d7cf"/>
|
||||||
|
<text x="14.0" y="615.3" fill="#2e3436" textLength="1006.2" lengthAdjust="spacing" xml:space="preserve"> v Laufwerke | c Aufraeumen | r Aktualisieren | q Beenden </text>
|
||||||
</svg>
|
</svg>
|
||||||
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 2.2 KiB |
@@ -65,7 +65,7 @@ welcher Snapshot offen ist, unten der volle Pfad des markierten Eintrags.
|
|||||||
| ++f3++ oder ++v++ | Datei ansehen (Text oder Hex) |
|
| ++f3++ oder ++v++ | Datei ansehen (Text oder Hex) |
|
||||||
| ++f7++ oder ++n++ | neues Verzeichnis (nur im lokalen Fenster) |
|
| ++f7++ oder ++n++ | neues Verzeichnis (nur im lokalen Fenster) |
|
||||||
| ++f6++ oder ++g++ | Verzeichnis direkt eingeben |
|
| ++f6++ oder ++g++ | Verzeichnis direkt eingeben |
|
||||||
| ++f2++ oder ++m++ | anderes Dateisystem des Snapshots wählen |
|
| ++f2++ oder ++m++ | [Laufwerk wählen](#laufwerk-wahlen) |
|
||||||
| ++r++ | neu einlesen |
|
| ++r++ | neu einlesen |
|
||||||
| ++f10++ oder ++q++ | eine Ebene zurück |
|
| ++f10++ oder ++q++ | eine Ebene zurück |
|
||||||
| ++question++ oder ++f1++ | Hilfe |
|
| ++question++ oder ++f1++ | Hilfe |
|
||||||
@@ -126,13 +126,18 @@ Datei erst auf den Host zu holen.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Mehrere Dateisysteme
|
## Laufwerk wählen
|
||||||
|
|
||||||
|
++f2++ öffnet eine Liste mit zwei Arten von Einträgen:
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
### Dateisysteme des Snapshots
|
||||||
|
|
||||||
Eine VM hat oft mehr als eine Partition: `/` und `/boot`, dazu vielleicht ein
|
Eine VM hat oft mehr als eine Partition: `/` und `/boot`, dazu vielleicht ein
|
||||||
eigenes Datenlaufwerk. Hat der Snapshot mehrere brauchbare Dateisysteme, fragt
|
eigenes Datenlaufwerk. Hat der Snapshot mehrere brauchbare Dateisysteme, fragt
|
||||||
der Explorer beim Öffnen, welches er zeigen soll.
|
der Explorer schon beim Öffnen, welches er zeigen soll — hier lässt sich
|
||||||
|
später ohne Schließen wechseln.
|
||||||
Wechseln geht später mit ++f2++, ohne den Snapshot zu schließen.
|
|
||||||
|
|
||||||
!!! note "Was nicht auftaucht"
|
!!! note "Was nicht auftaucht"
|
||||||
|
|
||||||
@@ -140,6 +145,42 @@ Wechseln geht später mit ++f2++, ohne den Snapshot zu schließen.
|
|||||||
Kernel nicht kennt. LVM innerhalb der VM wird erkannt und aktiviert;
|
Kernel nicht kennt. LVM innerhalb der VM wird erkannt und aktiviert;
|
||||||
verschlüsselte Volumes bleiben verschlüsselt.
|
verschlüsselte Volumes bleiben verschlüsselt.
|
||||||
|
|
||||||
|
### Austauschlaufwerke
|
||||||
|
|
||||||
|
Darunter stehen die
|
||||||
|
[Austauschlaufwerke](../wiederherstellen/transfer.md). Wählt man eines aus,
|
||||||
|
wird es am Host eingehängt und **im rechten Fenster** geöffnet.
|
||||||
|
|
||||||
|
Damit lässt sich aus einem Snapshot direkt auf das Austauschmedium kopieren,
|
||||||
|
das anschließend in eine [wiederhergestellte
|
||||||
|
Maschine](../wiederherstellen/index.md) wandert — ohne Umweg über
|
||||||
|
`pvesnap-recovery` und ohne Zwischenlager auf dem Host.
|
||||||
|
|
||||||
|
<div class="ablauf" markdown>
|
||||||
|
|
||||||
|
**Der typische Fall:** Vor dem Notfall die Werkzeuge zusammenstellen. Snapshot
|
||||||
|
öffnen, ++f2++, Austauschlaufwerk wählen, die gebrauchten Binärdateien und
|
||||||
|
Konfigurationen hinüberkopieren. Beim nächsten `pvesnap-recovery` steht alles
|
||||||
|
bereit.
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
| in der Liste | |
|
||||||
|
|---|---|
|
||||||
|
| ohne Zusatz | frei oder bereits am Host eingehängt — kann geöffnet werden |
|
||||||
|
| `[in Benutzung von 9101]` | steckt in einer Maschine. Öffnen wird abgelehnt; siehe [Host oder Gast, nie beides](../wiederherstellen/transfer.md#host-oder-gast-nie-beides) |
|
||||||
|
|
||||||
|
Beim Verlassen des Explorers wird ein so eingehängtes Laufwerk **automatisch
|
||||||
|
wieder ausgehängt** — sonst gälte es später als belegt und ließe sich nicht in
|
||||||
|
eine Maschine hängen. Ein Laufwerk, das schon vorher am Host hing, bleibt
|
||||||
|
unangetastet.
|
||||||
|
|
||||||
|
!!! note "Ohne Snapshot"
|
||||||
|
|
||||||
|
Wird der Explorer aus der [Laufwerksverwaltung](../wiederherstellen/transfer.md)
|
||||||
|
heraus geöffnet, gibt es kein Snapshot-Fenster. Dann landet das gewählte
|
||||||
|
Laufwerk im **gerade aktiven** Fenster.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Aufrufparameter
|
## Aufrufparameter
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ pvesnap-recovery <befehl> [optionen]
|
|||||||
| `console <VMID>` | noVNC-Adresse ausgeben |
|
| `console <VMID>` | noVNC-Adresse ausgeben |
|
||||||
| `spice <VMID>` | Verbindungsdatei für `remote-viewer` |
|
| `spice <VMID>` | Verbindungsdatei für `remote-viewer` |
|
||||||
| `flatten <VMID>` | [vom Quell-Snapshot lösen](../wiederherstellen/loesen.md) |
|
| `flatten <VMID>` | [vom Quell-Snapshot lösen](../wiederherstellen/loesen.md) |
|
||||||
| `cleanup` | [Klone entfernen, zu denen es keinen Gast mehr gibt](../wiederherstellen/verwerfen.md#cleanup) |
|
| `cleanup` | [aufräumen: Klone ohne Gast und geschützte Snapshots ohne Klon](../wiederherstellen/verwerfen.md#cleanup) |
|
||||||
| `destroy <VMID>` | [restlos verwerfen](../wiederherstellen/verwerfen.md) |
|
| `destroy <VMID>` | [restlos verwerfen](../wiederherstellen/verwerfen.md) |
|
||||||
|
|
||||||
### Optionen von `live` und `recover`
|
### Optionen von `live` und `recover`
|
||||||
|
|||||||
@@ -107,6 +107,7 @@ Ceph verlangt den Schutz, solange ein Klon existiert.
|
|||||||
| Sie wird nicht mehr gebraucht | `pvesnap-recovery destroy <VMID>` |
|
| Sie wird nicht mehr gebraucht | `pvesnap-recovery destroy <VMID>` |
|
||||||
| Sie soll bleiben | `pvesnap-recovery flatten <VMID>` |
|
| Sie soll bleiben | `pvesnap-recovery flatten <VMID>` |
|
||||||
| Sie wurde in der **Weboberfläche** gelöscht | `pvesnap-recovery cleanup` |
|
| Sie wurde in der **Weboberfläche** gelöscht | `pvesnap-recovery cleanup` |
|
||||||
|
| Es gibt gar keinen Klon mehr dazu | `pvesnap-recovery cleanup` — [Durchgang 2](../wiederherstellen/verwerfen.md#durchgang-2-geschutzte-snapshots-ohne-klon) findet genau das |
|
||||||
|
|
||||||
Der letzte Fall ist der, den man sonst nicht findet: Der Klon ist weg, der
|
Der letzte Fall ist der, den man sonst nicht findet: Der Klon ist weg, der
|
||||||
Schutz steht noch, und die Vorhaltezeit scheitert für immer.
|
Schutz steht noch, und die Vorhaltezeit scheitert für immer.
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ Alle Oberflächen auf einer Seite. Zum Ausdrucken und Danebenlegen.
|
|||||||
| ++f3++ ++v++ | Datei ansehen (Text oder Hex) |
|
| ++f3++ ++v++ | Datei ansehen (Text oder Hex) |
|
||||||
| ++f7++ ++n++ | neues Verzeichnis (nur lokal) |
|
| ++f7++ ++n++ | neues Verzeichnis (nur lokal) |
|
||||||
| ++f6++ ++g++ | Verzeichnis direkt eingeben |
|
| ++f6++ ++g++ | Verzeichnis direkt eingeben |
|
||||||
| ++f2++ ++m++ | anderes Dateisystem des Snapshots wählen |
|
| ++f2++ ++m++ | Laufwerk wählen: Dateisystem des Snapshots oder Austauschlaufwerk |
|
||||||
| ++r++ | neu einlesen |
|
| ++r++ | neu einlesen |
|
||||||
| ++f1++ ++question++ | Hilfe |
|
| ++f1++ ++question++ | Hilfe |
|
||||||
| ++f10++ ++q++ | eine Ebene zurück |
|
| ++f10++ ++q++ | eine Ebene zurück |
|
||||||
@@ -80,6 +80,7 @@ Während des Kopierens bricht ++esc++ ab.
|
|||||||
| ++h++ | herunterfahren |
|
| ++h++ | herunterfahren |
|
||||||
| ++x++ | verwerfen |
|
| ++x++ | verwerfen |
|
||||||
| ++v++ | Austauschlaufwerke |
|
| ++v++ | Austauschlaufwerke |
|
||||||
|
| ++c++ | aufräumen (Reste und verwaiste Snapshot-Sperren) |
|
||||||
| ++r++ | neu einlesen |
|
| ++r++ | neu einlesen |
|
||||||
| ++q++ | Ende |
|
| ++q++ | Ende |
|
||||||
|
|
||||||
|
|||||||
@@ -164,6 +164,7 @@ Vollständig unter [Alle Befehle](../nachschlagen/befehle.md#pvesnap-recovery).
|
|||||||
| ++s++ / ++h++ | starten / herunterfahren |
|
| ++s++ / ++h++ | starten / herunterfahren |
|
||||||
| ++x++ | [verwerfen](verwerfen.md) |
|
| ++x++ | [verwerfen](verwerfen.md) |
|
||||||
| ++v++ | [Austauschlaufwerke](transfer.md) |
|
| ++v++ | [Austauschlaufwerke](transfer.md) |
|
||||||
|
| ++c++ | [aufräumen](verwerfen.md#cleanup) — Reste und verwaiste Snapshot-Sperren |
|
||||||
| ++r++ | Liste neu einlesen |
|
| ++r++ | Liste neu einlesen |
|
||||||
| ++q++ | Ende |
|
| ++q++ | Ende |
|
||||||
|
|
||||||
|
|||||||
@@ -90,6 +90,13 @@ als beim Snapshot-Explorer, wo links Schreibschutz herrscht. Kopiert wird mit
|
|||||||
|
|
||||||
Beim Verlassen mit ++q++ wird das Laufwerk automatisch wieder ausgehängt.
|
Beim Verlassen mit ++q++ wird das Laufwerk automatisch wieder ausgehängt.
|
||||||
|
|
||||||
|
!!! tip "Auch direkt aus dem Explorer erreichbar"
|
||||||
|
|
||||||
|
Im [`pvesnap-explorer`](../holen/explorer.md#laufwerk-wahlen) öffnet ++f2++
|
||||||
|
dieselbe Auswahl. Das Laufwerk landet dann im rechten Fenster, links steht
|
||||||
|
weiter der Snapshot — damit lässt sich **direkt aus einem Snapshot auf das
|
||||||
|
Austauschmedium kopieren**, ohne Umweg über den Host.
|
||||||
|
|
||||||
!!! tip "Was hineingehört"
|
!!! tip "Was hineingehört"
|
||||||
|
|
||||||
Alles, was man im Gast bräuchte und dort nicht herunterladen kann:
|
Alles, was man im Gast bräuchte und dort nicht herunterladen kann:
|
||||||
@@ -212,6 +219,15 @@ Angelegt und verwaltet werden sie nur über die Oberfläche — dafür gibt es
|
|||||||
bewusst keine Parameter. Das ist eine Vorbereitungsaufgabe, keine, die man
|
bewusst keine Parameter. Das ist eine Vorbereitungsaufgabe, keine, die man
|
||||||
unter Zeitdruck tippt.
|
unter Zeitdruck tippt.
|
||||||
|
|
||||||
|
Erreichbar ist die Verwaltung von vier Stellen aus:
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| `pvesnap-recovery`, Übersicht | ++v++ |
|
||||||
|
| … Optionsmaske beim Anlegen | ++v++ |
|
||||||
|
| … Detailansicht einer Maschine | ++v++ |
|
||||||
|
| `pvesnap-explorer` | ++f2++ — [Laufwerk wählen](../holen/explorer.md#laufwerk-wahlen) |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Wo sie liegen
|
## Wo sie liegen
|
||||||
|
|||||||
@@ -71,11 +71,14 @@ Wiederherstellung an.
|
|||||||
## `cleanup`
|
## `cleanup`
|
||||||
|
|
||||||
Wird eine Wiederherstellung in der **Proxmox-Oberfläche** entfernt statt mit
|
Wird eine Wiederherstellung in der **Proxmox-Oberfläche** entfernt statt mit
|
||||||
`destroy`, bleibt etwas liegen:
|
`destroy`, bleibt etwas liegen. Je nachdem, wie gründlich Proxmox dabei war,
|
||||||
|
zwei verschiedene Dinge — deshalb sucht `cleanup` in **zwei Durchgängen**.
|
||||||
|
|
||||||
* der Klon selbst kann übrigbleiben
|
Taste ++c++ in der Übersicht:
|
||||||
* der Quell-Snapshot bleibt **geschützt** — die Vorhaltezeit scheitert dann
|
|
||||||
dauerhaft
|

|
||||||
|
|
||||||
|
Oder auf der Kommandozeile:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
pvesnap-recovery cleanup # zeigt Gefundenes, fragt, räumt auf
|
pvesnap-recovery cleanup # zeigt Gefundenes, fragt, räumt auf
|
||||||
@@ -83,10 +86,12 @@ pvesnap-recovery cleanup --all # auch Datenträger ohne Elternteil
|
|||||||
pvesnap-recovery cleanup -y # ohne Rückfrage
|
pvesnap-recovery cleanup -y # ohne Rückfrage
|
||||||
```
|
```
|
||||||
|
|
||||||
### Wonach gesucht wird
|
---
|
||||||
|
|
||||||
Nach Datenträgern der Form `vm-<id>-disk-N`, zu deren VMID es **keine
|
### Durchgang 1: Datenträger ohne Gast
|
||||||
Konfigurationsdatei** unter `/etc/pve/nodes/*/` mehr gibt.
|
|
||||||
|
Gesucht wird nach Datenträgern der Form `vm-<id>-disk-N`, zu deren VMID es
|
||||||
|
**keine Konfigurationsdatei** unter `/etc/pve/nodes/*/` mehr gibt.
|
||||||
|
|
||||||
!!! info "Bewusst nicht über `/cluster/resources`"
|
!!! info "Bewusst nicht über `/cluster/resources`"
|
||||||
|
|
||||||
@@ -104,10 +109,52 @@ Zwei weitere Vorsichtsmaßnahmen:
|
|||||||
| **Ohne Elternteil** wird nichts von selbst entfernt | dafür braucht es `--all` |
|
| **Ohne Elternteil** wird nichts von selbst entfernt | dafür braucht es `--all` |
|
||||||
| **Der Schutz eines Snapshots wird nur gelöst**, wenn wirklich kein Klon mehr daran hängt | sonst würde ein noch lebender Klon seine Grundlage verlieren |
|
| **Der Schutz eines Snapshots wird nur gelöst**, wenn wirklich kein Klon mehr daran hängt | sonst würde ein noch lebender Klon seine Grundlage verlieren |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Durchgang 2: Geschützte Snapshots ohne Klon
|
||||||
|
|
||||||
|
Der Fall, den der erste Durchgang **prinzipiell nicht finden kann**.
|
||||||
|
|
||||||
|
Löscht jemand die Wiederherstellung in der Proxmox-Oberfläche, räumt 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.
|
||||||
|
|
||||||
|
Zurück bleibt ein geschützter Snapshot ohne Klon:
|
||||||
|
|
||||||
|
!!! danger "Und danach scheitert die Vorhaltezeit für immer"
|
||||||
|
|
||||||
|
```
|
||||||
|
TASK ERROR: rbd snapshot 'auto-taeglich-20260809-023000' is protected from removal
|
||||||
|
```
|
||||||
|
|
||||||
|
Jede Nacht aufs Neue, an derselben Stelle. Nach einem liegengebliebenen
|
||||||
|
Datenträger sucht man dabei vergeblich — der ist ja tatsächlich weg.
|
||||||
|
|
||||||
|
Derselbe Zustand entsteht nach einem von Hand ausgeführten `rbd flatten`: Der
|
||||||
|
Klon hängt danach an nichts mehr, der Schutz steht aber weiter.
|
||||||
|
|
||||||
|
Gesucht wird deshalb direkt nach der Ursache: **jeder geschützte Snapshot, an
|
||||||
|
dem kein Klon hängt.** Ein solcher Schutz hat keinen Zweck — er existiert
|
||||||
|
einzig dafür, dass ein Klon seine Grundlage behält.
|
||||||
|
|
||||||
|
!!! success "Gelöst wird nur der Schutz, gelöscht wird nichts"
|
||||||
|
|
||||||
|
Die Snapshots bleiben stehen. Danach darf die Vorhaltezeit sie wieder
|
||||||
|
wegräumen, wenn sie alt genug sind — mehr passiert nicht.
|
||||||
|
|
||||||
|
Vor jedem Lösen wird ein zweites Mal nachgesehen, ob inzwischen doch ein
|
||||||
|
Klon daran hängt. Zwischen Suchen und Aufräumen kann jemand eine neue
|
||||||
|
Wiederherstellung angelegt haben; der bekommt man nicht die Grundlage unter
|
||||||
|
den Füßen weg.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### Wann man es braucht
|
### Wann man es braucht
|
||||||
|
|
||||||
* Jemand hat eine Wiederherstellung in der Weboberfläche gelöscht
|
* Jemand hat eine Wiederherstellung in der Weboberfläche gelöscht
|
||||||
* Ein `destroy` ist mittendrin abgebrochen (Host neu gestartet, Netz weg)
|
* Ein `destroy` ist mittendrin abgebrochen (Host neu gestartet, Netz weg)
|
||||||
|
* Es wurde von Hand geflattet
|
||||||
* Im Protokoll des Dienstes taucht wiederholt `snapshot is protected` auf
|
* Im Protokoll des Dienstes taucht wiederholt `snapshot is protected` auf
|
||||||
|
|
||||||
Der letzte Fall ist der häufigste — und der, den man sonst lange sucht.
|
Der letzte Fall ist der häufigste — und der, den man sonst lange sucht.
|
||||||
|
|||||||
@@ -82,9 +82,15 @@ def handle_get(parts, args, data):
|
|||||||
"content": "iso,vztmpl,backup"},
|
"content": "iso,vztmpl,backup"},
|
||||||
}.get(parts[1], _MISS)
|
}.get(parts[1], _MISS)
|
||||||
if parts == ["storage"]:
|
if parts == ["storage"]:
|
||||||
return [{"storage": "ceph-vm", "type": "rbd", "shared": 1},
|
# Die Liste traegt dieselben Felder wie der Einzelabruf - besonders
|
||||||
{"storage": "ceph-ct", "type": "rbd", "shared": 1},
|
# "pool". Ohne das wuerde jeder rbd-Aufruf im Standard-Pool landen.
|
||||||
{"storage": "local", "type": "dir", "shared": 0}]
|
return [{"storage": "ceph-vm", "type": "rbd", "shared": 1,
|
||||||
|
"pool": "vmdaten", "content": "images",
|
||||||
|
"monhost": "10.20.0.11 10.20.0.12"},
|
||||||
|
{"storage": "ceph-ct", "type": "rbd", "shared": 1,
|
||||||
|
"pool": "ctdaten", "content": "rootdir"},
|
||||||
|
{"storage": "local", "type": "dir", "shared": 0,
|
||||||
|
"content": "iso,vztmpl,backup"}]
|
||||||
if parts == ["cluster", "nextid"]:
|
if parts == ["cluster", "nextid"]:
|
||||||
wanted = args.get("vmid")
|
wanted = args.get("vmid")
|
||||||
if wanted:
|
if wanted:
|
||||||
|
|||||||
@@ -1,67 +1,149 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""Attrappe von `rbd` - nur lesende Auskuenfte fuer die Bildschirmfotos.
|
"""Attrappe von `rbd` - lesende Auskuenfte fuer Bildschirmfotos und Tests.
|
||||||
|
|
||||||
VM 9101 ist ein Linked Clone (haengt am Quell-Snapshot), VM 9102 ist bereits
|
Die Kulisse enthaelt bewusst beide Zustaende, um die es beim Aufraeumen geht:
|
||||||
geloest. Genau diese beiden Zustaende soll das Handbuch zeigen.
|
|
||||||
|
vm-101-disk-0@auto-stuendlich-20260809-100000 geschuetzt, MIT Klon
|
||||||
|
-> muss in Ruhe gelassen werden
|
||||||
|
vm-105-disk-0@auto-taeglich-20260809-023000 geschuetzt, OHNE Klon
|
||||||
|
-> genau der Fall, den
|
||||||
|
find_stale_protections sucht
|
||||||
|
vm-100-disk-0@auto-taeglich-20260809-023000 ungeschuetzt
|
||||||
|
-> geht niemanden etwas an
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
VERBUNDEN = "vm-9101-disk-0" # haengt noch am Snapshot
|
# Abbild -> Elternteil (nur Klone haben einen)
|
||||||
QUELLE = "vmdaten/vm-101-disk-0@auto-stuendlich-20260809-100000"
|
ELTERN = {
|
||||||
|
"vm-9101-disk-0": "vmdaten/vm-101-disk-0@auto-stuendlich-20260809-100000",
|
||||||
|
}
|
||||||
|
|
||||||
|
# Abbild -> [(Snapshot, geschuetzt?)]
|
||||||
|
SNAPSHOTS = {
|
||||||
|
"vm-100-disk-0": [("auto-taeglich-20260809-023000", False)],
|
||||||
|
"vm-101-disk-0": [("auto-taeglich-20260809-023000", False),
|
||||||
|
("auto-stuendlich-20260809-100000", True)],
|
||||||
|
"vm-105-disk-0": [("auto-taeglich-20260809-023000", True),
|
||||||
|
("golden", False)],
|
||||||
|
}
|
||||||
|
|
||||||
|
POOLS = {
|
||||||
|
"vmdaten": ["vm-100-disk-0", "vm-101-disk-0", "vm-102-disk-0",
|
||||||
|
"vm-105-disk-0", "vm-130-disk-0",
|
||||||
|
"vm-9101-disk-0", "vm-9102-disk-0"],
|
||||||
|
"ctdaten": ["subvol-110-disk-0", "subvol-111-disk-0"],
|
||||||
|
}
|
||||||
|
|
||||||
|
GROESSEN = {"vm-101-disk-0": 400, "vm-105-disk-0": 250, "vm-9101-disk-0": 400}
|
||||||
|
|
||||||
# Optionen, hinter denen noch ein Wert steht - sonst haelt man "-m <monhost>"
|
# Optionen, hinter denen noch ein Wert steht - sonst haelt man "-m <monhost>"
|
||||||
# fuer den Befehl.
|
# fuer den Befehl.
|
||||||
MIT_WERT = {"-m", "--id", "--keyring", "-c", "--conf", "-p", "--pool",
|
MIT_WERT = {"-m", "--id", "--keyring", "-c", "--conf", "-p", "--pool",
|
||||||
"--namespace", "-n", "--name"}
|
"--namespace", "-n", "--name", "--snap", "--format"}
|
||||||
|
|
||||||
|
|
||||||
def zerlege(argv):
|
def zerlege(argv):
|
||||||
"""(Befehl, Abbild) aus der Kommandozeile."""
|
"""(Woerter ohne Optionen, {Option: Wert}) aus der Kommandozeile."""
|
||||||
rest = []
|
rest, optionen = [], {}
|
||||||
index = 0
|
index = 0
|
||||||
while index < len(argv):
|
while index < len(argv):
|
||||||
wort = argv[index]
|
wort = argv[index]
|
||||||
if wort in MIT_WERT:
|
if wort in MIT_WERT:
|
||||||
|
optionen[wort] = argv[index + 1] if index + 1 < len(argv) else ""
|
||||||
index += 2
|
index += 2
|
||||||
continue
|
continue
|
||||||
if wort.startswith("-"):
|
if wort.startswith("-"):
|
||||||
|
optionen[wort] = True
|
||||||
index += 1
|
index += 1
|
||||||
continue
|
continue
|
||||||
rest.append(wort)
|
rest.append(wort)
|
||||||
index += 1
|
index += 1
|
||||||
return (rest[0] if rest else ""), (rest[1] if len(rest) > 1 else "")
|
return rest, optionen
|
||||||
|
|
||||||
|
|
||||||
|
def teile(ziel):
|
||||||
|
"""pool/abbild@snap -> (abbild, snap)"""
|
||||||
|
ohne_pool = ziel.split("/")[-1]
|
||||||
|
abbild, _, snap = ohne_pool.partition("@")
|
||||||
|
return abbild, snap
|
||||||
|
|
||||||
|
|
||||||
|
def kinder(abbild, snap):
|
||||||
|
return [k for k, eltern in ELTERN.items()
|
||||||
|
if eltern.endswith("/%s@%s" % (abbild, snap))]
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
if not argv:
|
rest, optionen = zerlege(argv)
|
||||||
|
if not rest:
|
||||||
return 1
|
return 1
|
||||||
befehl, ziel = zerlege(argv)
|
befehl = rest[0]
|
||||||
name = ziel.split("/")[-1].split("@")[0]
|
ziel = rest[1] if len(rest) > 1 else ""
|
||||||
|
abbild, snap = teile(ziel)
|
||||||
|
|
||||||
|
if befehl == "ls":
|
||||||
|
for name in POOLS.get(ziel or "vmdaten", []):
|
||||||
|
print(name)
|
||||||
|
return 0
|
||||||
|
|
||||||
if befehl == "info":
|
if befehl == "info":
|
||||||
print("rbd image '%s':" % name)
|
eltern = ELTERN.get(abbild)
|
||||||
print("\tsize 400 GiB in 102400 objects")
|
gigabyte = GROESSEN.get(abbild, 32)
|
||||||
print("\torder 22 (4 MiB objects)")
|
if optionen.get("--format") == "json":
|
||||||
print("\tsnapshot_count: 0")
|
daten = {"name": abbild, "size": gigabyte * 1024 ** 3, "format": 2,
|
||||||
print("\tid: 1f5c3d9a7b2e")
|
"snapshot_count": len(SNAPSHOTS.get(abbild, []))}
|
||||||
print("\tblock_name_prefix: rbd_data.1f5c3d9a7b2e")
|
if eltern:
|
||||||
|
pool, _, rest_ = eltern.partition("/")
|
||||||
|
bild, _, s = rest_.partition("@")
|
||||||
|
daten["parent"] = {"pool": pool, "image": bild, "snapshot": s}
|
||||||
|
print(json.dumps(daten))
|
||||||
|
return 0
|
||||||
|
print("rbd image '%s':" % abbild)
|
||||||
|
print("\tsize %d GiB in %d objects" % (gigabyte, gigabyte * 256))
|
||||||
print("\tformat: 2")
|
print("\tformat: 2")
|
||||||
print("\tfeatures: layering, exclusive-lock")
|
print("\tfeatures: layering, exclusive-lock")
|
||||||
print("\top_features:")
|
if eltern:
|
||||||
print("\tflags:")
|
print("\tparent: %s" % eltern)
|
||||||
if name == VERBUNDEN:
|
print("\toverlap: %d GiB" % gigabyte)
|
||||||
print("\tparent: %s" % QUELLE)
|
|
||||||
print("\toverlap: 400 GiB")
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
if befehl == "du":
|
if befehl == "snap" and len(rest) > 1 and rest[1] == "ls":
|
||||||
|
abbild, _ = teile(rest[2] if len(rest) > 2 else "")
|
||||||
|
eintraege = [{"id": nummer + 1, "name": name,
|
||||||
|
"size": GROESSEN.get(abbild, 32) * 1024 ** 3,
|
||||||
|
"protected": "true" if geschuetzt else "false"}
|
||||||
|
for nummer, (name, geschuetzt)
|
||||||
|
in enumerate(SNAPSHOTS.get(abbild, []))]
|
||||||
|
if optionen.get("--format") == "json":
|
||||||
|
print(json.dumps(eintraege))
|
||||||
|
else:
|
||||||
|
for e in eintraege:
|
||||||
|
print("%4d %-40s %10d" % (e["id"], e["name"], e["size"]))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if befehl == "snap" and len(rest) > 1 and rest[1] in ("unprotect", "protect"):
|
||||||
|
abbild, _ = teile(rest[2] if len(rest) > 2 else "")
|
||||||
|
name = optionen.get("--snap") or ""
|
||||||
|
vorhanden = [s for s, _ in SNAPSHOTS.get(abbild, [])]
|
||||||
|
if name not in vorhanden:
|
||||||
|
print("rbd: snapshot '%s' does not exist" % name, file=sys.stderr)
|
||||||
|
return 2
|
||||||
|
if rest[1] == "unprotect" and kinder(abbild, name):
|
||||||
|
print("rbd: snapshot '%s' is protected from unprotection" % name,
|
||||||
|
file=sys.stderr)
|
||||||
|
return 16
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if befehl == "children":
|
||||||
|
for kind in kinder(abbild, snap):
|
||||||
|
print("vmdaten/%s" % kind)
|
||||||
|
return 0
|
||||||
|
|
||||||
|
if befehl in ("du", "flatten"):
|
||||||
print("NAME PROVISIONED USED")
|
print("NAME PROVISIONED USED")
|
||||||
print("%-19s 400 GiB 2.4 GiB" % name)
|
print("%-19s 400 GiB 2.4 GiB" % abbild)
|
||||||
return 0
|
|
||||||
|
|
||||||
if befehl in ("flatten", "snap"):
|
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
return 0
|
return 0
|
||||||
|
|||||||
@@ -61,13 +61,29 @@ def _patch():
|
|||||||
tui.service_text = lambda: "Dienst: laeuft"
|
tui.service_text = lambda: "Dienst: laeuft"
|
||||||
|
|
||||||
|
|
||||||
def _explorer_panes(links_pfad, links_titel, untertitel, links_schreibbar=False):
|
class Kulissensession:
|
||||||
|
"""Genau so viel Sitzung, wie der Explorer fuer die Laufwerkswahl braucht.
|
||||||
|
|
||||||
|
Eine echte Session wuerde einen Snapshot einbinden - dafuer braucht es
|
||||||
|
Ceph. Der Explorer fasst sie nur an zwei Stellen an: um die Dateisysteme
|
||||||
|
aufzuzaehlen und um zu wissen, welches Fenster der Snapshot ist.
|
||||||
|
"""
|
||||||
|
mounts = {"root": 1, "boot": 1}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def mount_entries():
|
||||||
|
return [(SNAP_MNT, "/ (ext4, 400G) - wahrscheinlich das System"),
|
||||||
|
(SNAP_MNT + "-boot", "/boot (ext4, 1G)")]
|
||||||
|
|
||||||
|
|
||||||
|
def _explorer_panes(links_pfad, links_titel, untertitel, links_schreibbar=False,
|
||||||
|
session=None):
|
||||||
from pvesnap.explorer import Explorer, Pane
|
from pvesnap.explorer import Explorer, Pane
|
||||||
links = Pane(path=links_pfad, root=links_pfad,
|
links = Pane(path=links_pfad, root=links_pfad,
|
||||||
readonly=not links_schreibbar, title=links_titel)
|
readonly=not links_schreibbar, title=links_titel)
|
||||||
rechts = Pane(path=_oder(LOKAL, os.path.join(DEMO, "lokal")), root="/",
|
rechts = Pane(path=_oder(LOKAL, os.path.join(DEMO, "lokal")), root="/",
|
||||||
readonly=False, title="Lokaler Rechner")
|
readonly=False, title="Lokaler Rechner")
|
||||||
return Explorer(links, rechts, subtitle=untertitel)
|
return Explorer(links, rechts, session=session, subtitle=untertitel)
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
@@ -98,8 +114,8 @@ def szene_transfer(stdscr):
|
|||||||
def szene_explorer(stdscr):
|
def szene_explorer(stdscr):
|
||||||
_explorer_panes(_oder(SNAP_MNT, os.path.join(DEMO, "snapshot")),
|
_explorer_panes(_oder(SNAP_MNT, os.path.join(DEMO, "snapshot")),
|
||||||
"VM 101 (db01) @ auto-taeglich-20260809-023000",
|
"VM 101 (db01) @ auto-taeglich-20260809-023000",
|
||||||
"VM 101 (db01) - Snapshot auto-taeglich-20260809-023000"
|
"VM 101 (db01) - Snapshot auto-taeglich-20260809-023000",
|
||||||
).run(stdscr)
|
session=Kulissensession()).run(stdscr)
|
||||||
|
|
||||||
|
|
||||||
def szene_explorer_transfer(stdscr):
|
def szene_explorer_transfer(stdscr):
|
||||||
|
|||||||
@@ -51,6 +51,8 @@ AUFNAHMEN = [
|
|||||||
("explorer-ansehen", "explorer",
|
("explorer-ansehen", "explorer",
|
||||||
["enter", "ab", "ab", "ab", "ab", "f3"],
|
["enter", "ab", "ab", "ab", "ab", "f3"],
|
||||||
"root@pve1 — pvesnap-explorer 101"),
|
"root@pve1 — pvesnap-explorer 101"),
|
||||||
|
("explorer-laufwerke", "explorer", ["f2"],
|
||||||
|
"root@pve1 — pvesnap-explorer 101"),
|
||||||
("explorer-hilfe", "explorer", ["?"],
|
("explorer-hilfe", "explorer", ["?"],
|
||||||
"root@pve1 — pvesnap-explorer 101"),
|
"root@pve1 — pvesnap-explorer 101"),
|
||||||
|
|
||||||
@@ -79,6 +81,8 @@ AUFNAHMEN = [
|
|||||||
"root@pve1 — pvesnap-recovery", 4.0),
|
"root@pve1 — pvesnap-recovery", 4.0),
|
||||||
("recovery-verwerfen", "recovery", ["x"],
|
("recovery-verwerfen", "recovery", ["x"],
|
||||||
"root@pve1 — pvesnap-recovery"),
|
"root@pve1 — pvesnap-recovery"),
|
||||||
|
("recovery-aufraeumen", "recovery", ["c"],
|
||||||
|
"root@pve1 — pvesnap-recovery", 3.0),
|
||||||
|
|
||||||
# -- Austauschlaufwerke -------------------------------------------------
|
# -- Austauschlaufwerke -------------------------------------------------
|
||||||
("transfer-uebersicht", "transfer", [],
|
("transfer-uebersicht", "transfer", [],
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ from dataclasses import dataclass, field
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from . import __version__
|
from . import __version__
|
||||||
|
from . import transfer
|
||||||
from .curses_util import (C_DIM, C_DIR, C_FOOTER, C_HEADER, C_MARK, C_OK, C_SEL,
|
from .curses_util import (C_DIM, C_DIR, C_FOOTER, C_HEADER, C_MARK, C_OK, C_SEL,
|
||||||
C_WARN, ask, choose, confirm, fill, init_colors, is_enter,
|
C_WARN, ask, choose, confirm, fill, init_colors, is_enter,
|
||||||
is_escape, keybar, message, prompt, put, read_key)
|
is_escape, keybar, message, prompt, put, read_key)
|
||||||
@@ -378,6 +379,10 @@ class Explorer:
|
|||||||
self.session = session
|
self.session = session
|
||||||
self.subtitle = subtitle
|
self.subtitle = subtitle
|
||||||
self.status = ""
|
self.status = ""
|
||||||
|
# Transfer-Laufwerke, die *wir* eingehaengt haben. Nur die haengen wir
|
||||||
|
# beim Verlassen wieder aus - ein Laufwerk, das schon vorher am Host
|
||||||
|
# hing, gehoert jemand anderem.
|
||||||
|
self.mounted_transfers = set()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def source(self):
|
def source(self):
|
||||||
@@ -494,7 +499,15 @@ class Explorer:
|
|||||||
def run(self, win):
|
def run(self, win):
|
||||||
for pane in self.panes:
|
for pane in self.panes:
|
||||||
pane.reload()
|
pane.reload()
|
||||||
|
try:
|
||||||
|
self._loop(win)
|
||||||
|
finally:
|
||||||
|
# Auch bei Absturz oder Strg-C: ein Transfer-Laufwerk, das am Host
|
||||||
|
# eingehaengt bleibt, gilt spaeter als belegt und laesst sich dann
|
||||||
|
# nicht in eine Maschine haengen.
|
||||||
|
self._release_transfers()
|
||||||
|
|
||||||
|
def _loop(self, win):
|
||||||
while True:
|
while True:
|
||||||
self.draw(win)
|
self.draw(win)
|
||||||
win.refresh()
|
win.refresh()
|
||||||
@@ -661,14 +674,85 @@ class Explorer:
|
|||||||
message(win, "Kein Verzeichnis: %s" % path, error=True)
|
message(win, "Kein Verzeichnis: %s" % path, error=True)
|
||||||
|
|
||||||
def _switch_mount(self, win):
|
def _switch_mount(self, win):
|
||||||
if not self.session or len(self.session.mounts) < 2:
|
"""Laufwerk waehlen - Dateisystem des Snapshots oder Transfer-Laufwerk.
|
||||||
message(win, "Es ist nur ein Dateisystem eingebunden.")
|
|
||||||
|
Beides steht in derselben Liste, weil es aus Sicht des Bedieners
|
||||||
|
dieselbe Frage ist: "wo soll ich hinschauen?". Ein Transfer-Laufwerk
|
||||||
|
hier zu erreichen spart den Umweg ueber pvesnap-recovery - man kann
|
||||||
|
aus dem Snapshot direkt auf das Austauschmedium kopieren, das
|
||||||
|
anschliessend in die wiederhergestellte Maschine wandert.
|
||||||
|
"""
|
||||||
|
entries = []
|
||||||
|
|
||||||
|
filesystems = self.session.mount_entries() if self.session else []
|
||||||
|
if len(filesystems) > 1:
|
||||||
|
for path, text in filesystems:
|
||||||
|
entries.append((("fs", path), "Snapshot: %s" % text))
|
||||||
|
|
||||||
|
for volume in self._transfer_volumes():
|
||||||
|
state = "" if volume.free or volume.mountpoint \
|
||||||
|
else " [%s]" % volume.state_text
|
||||||
|
entries.append((("transfer", volume.name),
|
||||||
|
"Transfer: %-14s %8s %s%s"
|
||||||
|
% (volume.name, human_bytes(volume.size),
|
||||||
|
truncate(volume.note, 26), state)))
|
||||||
|
|
||||||
|
if not entries:
|
||||||
|
message(win, "Es ist nur ein Dateisystem eingebunden, und "
|
||||||
|
"Transfer-Laufwerke gibt es noch keine.")
|
||||||
return
|
return
|
||||||
entries = self.session.mount_entries()
|
|
||||||
chosen = choose(win, "Welches Dateisystem?", entries)
|
chosen = choose(win, "Welches Laufwerk?", entries)
|
||||||
if chosen:
|
if not chosen:
|
||||||
self.panes[0].root = chosen
|
return
|
||||||
self.panes[0].go(chosen)
|
kind, value = chosen
|
||||||
|
if kind == "fs":
|
||||||
|
self.panes[0].root = value
|
||||||
|
self.panes[0].go(value)
|
||||||
|
else:
|
||||||
|
self._open_transfer(win, value)
|
||||||
|
|
||||||
|
def _transfer_volumes(self):
|
||||||
|
try:
|
||||||
|
return transfer.load()
|
||||||
|
except transfer.TransferError as exc:
|
||||||
|
self.status = str(exc)
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _open_transfer(self, win, name):
|
||||||
|
"""Ein Transfer-Laufwerk einhaengen und ins Arbeitsfenster holen.
|
||||||
|
|
||||||
|
Ins Arbeitsfenster, nicht ins Snapshot-Fenster: links steht der
|
||||||
|
Snapshot, aus dem geholt wird, rechts das Ziel. Genau dorthin gehoert
|
||||||
|
ein Austauschmedium. Gibt es keinen Snapshot (Aufruf aus der
|
||||||
|
Laufwerksverwaltung heraus), landet es im gerade aktiven Fenster.
|
||||||
|
"""
|
||||||
|
target = self.panes[1] if self.session else self.source
|
||||||
|
try:
|
||||||
|
already = bool(transfer.get(name).mountpoint)
|
||||||
|
path = transfer.host_mount(name)
|
||||||
|
except transfer.TransferError as exc:
|
||||||
|
message(win, str(exc), error=True)
|
||||||
|
return
|
||||||
|
if not already:
|
||||||
|
self.mounted_transfers.add(name)
|
||||||
|
|
||||||
|
target.root = "/"
|
||||||
|
target.marks.clear()
|
||||||
|
if not target.go(path):
|
||||||
|
message(win, "Eingehaengt unter %s, aber nicht lesbar." % path, error=True)
|
||||||
|
return
|
||||||
|
target.title = "Transfer: %s" % name
|
||||||
|
self.status = "%s eingehaengt unter %s" % (name, path)
|
||||||
|
|
||||||
|
def _release_transfers(self):
|
||||||
|
"""Was wir eingehaengt haben, geben wir auch wieder frei."""
|
||||||
|
for name in sorted(self.mounted_transfers):
|
||||||
|
try:
|
||||||
|
transfer.host_umount(name)
|
||||||
|
except transfer.TransferError as exc:
|
||||||
|
log.warning("Transfer-Laufwerk %s blieb eingehaengt: %s", name, exc)
|
||||||
|
self.mounted_transfers.clear()
|
||||||
|
|
||||||
def _view(self, win, entry):
|
def _view(self, win, entry):
|
||||||
if not entry.name or entry.is_dir or entry.is_special:
|
if not entry.name or entry.is_dir or entry.is_special:
|
||||||
@@ -732,7 +816,8 @@ class Explorer:
|
|||||||
"F3 / v Datei ansehen (Text oder Hex)",
|
"F3 / v Datei ansehen (Text oder Hex)",
|
||||||
"F7 / n neues Verzeichnis (nur im lokalen Fenster)",
|
"F7 / n neues Verzeichnis (nur im lokalen Fenster)",
|
||||||
"F6 / g Verzeichnis direkt eingeben",
|
"F6 / g Verzeichnis direkt eingeben",
|
||||||
"F2 / m anderes Dateisystem des Snapshots waehlen",
|
"F2 / m Laufwerk waehlen: anderes Dateisystem des Snapshots",
|
||||||
|
" oder ein Transfer-Laufwerk ins rechte Fenster",
|
||||||
"r neu einlesen",
|
"r neu einlesen",
|
||||||
"F10 / q zurueck zur Snapshot-Auswahl",
|
"F10 / q zurueck zur Snapshot-Auswahl",
|
||||||
"",
|
"",
|
||||||
@@ -741,6 +826,10 @@ class Explorer:
|
|||||||
"Geraetedateien und Sockets werden uebersprungen, symbolische",
|
"Geraetedateien und Sockets werden uebersprungen, symbolische",
|
||||||
"Verweise bleiben Verweise.",
|
"Verweise bleiben Verweise.",
|
||||||
"",
|
"",
|
||||||
|
"Transfer-Laufwerke (F2) sind Austauschmedien fuer Maschinen ohne",
|
||||||
|
"Netz: hier befuellen, spaeter in die Wiederherstellung haengen.",
|
||||||
|
"Beim Verlassen werden sie wieder ausgehaengt.",
|
||||||
|
"",
|
||||||
"Es gibt drei Ebenen: Gastauswahl -> Snapshot-Auswahl -> Dateien.",
|
"Es gibt drei Ebenen: Gastauswahl -> Snapshot-Auswahl -> Dateien.",
|
||||||
"F10 oder q geht jeweils eine Ebene zurueck; der Snapshot wird",
|
"F10 oder q geht jeweils eine Ebene zurueck; der Snapshot wird",
|
||||||
"dabei wieder ausgehaengt. Das rechte Fenster behaelt sein",
|
"dabei wieder ausgehaengt. Das rechte Fenster behaelt sein",
|
||||||
|
|||||||
@@ -1535,18 +1535,7 @@ def find_orphans(proxmox):
|
|||||||
"stimmen. Zur Sicherheit wird nichts angefasst." % CONF_ROOT)
|
"stimmen. Zur Sicherheit wird nichts angefasst." % CONF_ROOT)
|
||||||
|
|
||||||
found = []
|
found = []
|
||||||
for storage in proxmox._json(["get", "/storage"]) or []:
|
for name, args, pool, prefix in _rbd_storages(proxmox):
|
||||||
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 image in run(args + ["ls", pool.rstrip("/")], check=False,
|
for image in run(args + ["ls", pool.rstrip("/")], check=False,
|
||||||
timeout=120).splitlines():
|
timeout=120).splitlines():
|
||||||
image = image.strip()
|
image = image.strip()
|
||||||
@@ -1605,6 +1594,118 @@ def remove_orphans(proxmox, orphans, progress=None):
|
|||||||
return removed, failed
|
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):
|
def destroy(proxmox, instance, progress=None, keep_snapshot_protection=False):
|
||||||
"""Eine Wiederherstellung restlos entfernen."""
|
"""Eine Wiederherstellung restlos entfernen."""
|
||||||
def step(text):
|
def step(text):
|
||||||
|
|||||||
@@ -29,10 +29,11 @@ from .curses_util import (C_DIM, C_FOOTER, C_HEADER, C_MARK, C_OK, C_SEL, C_WARN
|
|||||||
from .proxmox import Proxmox, ProxmoxError, pvesh_available
|
from .proxmox import Proxmox, ProxmoxError, pvesh_available
|
||||||
from . import transfer, transfer_ui
|
from . import transfer, transfer_ui
|
||||||
from .recovery import (RecoveryError, Spec, create, destroy, eject_transfer,
|
from .recovery import (RecoveryError, Spec, create, destroy, eject_transfer,
|
||||||
find_orphans, flatten, flatten_cost, flatten_hint,
|
find_orphans, find_stale_protections, flatten,
|
||||||
|
flatten_cost, flatten_hint,
|
||||||
human_bytes, insert_transfer, linked_volumes,
|
human_bytes, insert_transfer, linked_volumes,
|
||||||
display_state, list_instances, node_address, plan,
|
display_state, list_instances, node_address, plan,
|
||||||
remove_orphans, set_display,
|
release_protections, remove_orphans, set_display,
|
||||||
spice_file, start, transfer_state)
|
spice_file, start, transfer_state)
|
||||||
from .snapfs import list_snapshots
|
from .snapfs import list_snapshots
|
||||||
from .util import truncate
|
from .util import truncate
|
||||||
@@ -46,6 +47,7 @@ KEYS = [
|
|||||||
("h", "Herunterfahren", "Stop"),
|
("h", "Herunterfahren", "Stop"),
|
||||||
("x", "Verwerfen", "Verwerf."),
|
("x", "Verwerfen", "Verwerf."),
|
||||||
("v", "Laufwerke", "Laufw."),
|
("v", "Laufwerke", "Laufw."),
|
||||||
|
("c", "Aufraeumen", "Aufr."),
|
||||||
("r", "Aktualisieren", "Aktual."),
|
("r", "Aktualisieren", "Aktual."),
|
||||||
("q", "Beenden", "Ende"),
|
("q", "Beenden", "Ende"),
|
||||||
]
|
]
|
||||||
@@ -878,6 +880,9 @@ def _overview(stdscr, proxmox):
|
|||||||
elif key in ("v", "V"):
|
elif key in ("v", "V"):
|
||||||
transfer_ui.screen(stdscr, browse=_browse_transfer)
|
transfer_ui.screen(stdscr, browse=_browse_transfer)
|
||||||
reload_ = True
|
reload_ = True
|
||||||
|
elif key in ("c", "C"):
|
||||||
|
_cleanup(stdscr, proxmox)
|
||||||
|
reload_ = True
|
||||||
elif key in ("r", "R"):
|
elif key in ("r", "R"):
|
||||||
reload_ = True
|
reload_ = True
|
||||||
elif key == curses.KEY_UP and instances:
|
elif key == curses.KEY_UP and instances:
|
||||||
@@ -901,6 +906,80 @@ def _overview(stdscr, proxmox):
|
|||||||
reload_ = True
|
reload_ = True
|
||||||
|
|
||||||
|
|
||||||
|
def _cleanup(stdscr, proxmox):
|
||||||
|
"""Reste aufraeumen, die entstehen, wenn jemand an pvesnap vorbei loescht.
|
||||||
|
|
||||||
|
Zwei verschiedene Dinge, deshalb zwei Durchgaenge: liegengebliebene
|
||||||
|
Datentraeger - und geschuetzte Snapshots, deren Klon es nicht mehr gibt.
|
||||||
|
Den zweiten Fall findet die Suche nach Datentraegern prinzipiell nicht.
|
||||||
|
"""
|
||||||
|
_wait_screen(stdscr, "Suche liegengebliebene Datentraeger ...")
|
||||||
|
try:
|
||||||
|
orphans = find_orphans(proxmox)
|
||||||
|
except (RecoveryError, ProxmoxError) as exc:
|
||||||
|
message(stdscr, str(exc), error=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
fortschritt = _Progress(stdscr, "Suche geschuetzte Snapshots ohne Klon")
|
||||||
|
try:
|
||||||
|
stale = find_stale_protections(proxmox, progress=fortschritt)
|
||||||
|
except (RecoveryError, ProxmoxError) as exc:
|
||||||
|
message(stdscr, str(exc), error=True)
|
||||||
|
return
|
||||||
|
|
||||||
|
klone = [e for e in orphans if e["parent"]]
|
||||||
|
if not klone and not stale:
|
||||||
|
message(stdscr, "Nichts liegengeblieben - alles sauber.")
|
||||||
|
return
|
||||||
|
|
||||||
|
zeilen = []
|
||||||
|
if klone:
|
||||||
|
zeilen.append(("Datentraeger, zu denen es keinen Gast mehr gibt:", curses.A_BOLD))
|
||||||
|
for entry in klone:
|
||||||
|
zeilen.append((" %-26s %8s Klon von %s"
|
||||||
|
% (entry["volid"], human_bytes(entry["bytes"]),
|
||||||
|
entry["parent"]), curses.color_pair(C_MARK)))
|
||||||
|
zeilen.append(("", 0))
|
||||||
|
if stale:
|
||||||
|
zeilen.append(("Geschuetzte Snapshots, an denen kein Klon mehr haengt:",
|
||||||
|
curses.A_BOLD))
|
||||||
|
for entry in stale:
|
||||||
|
zeilen.append((" %s@%s" % (entry["volid"], entry["snapshot"]),
|
||||||
|
curses.color_pair(C_MARK)))
|
||||||
|
zeilen.append(("", 0))
|
||||||
|
for text in _wrap("An diesen scheitert die Vorhaltezeit dauerhaft mit "
|
||||||
|
"\"snapshot is protected\". Der Schutz wird geloest - "
|
||||||
|
"geloescht wird dabei nichts.", 74):
|
||||||
|
zeilen.append((text, curses.color_pair(C_DIM)))
|
||||||
|
zeilen.append(("", 0))
|
||||||
|
|
||||||
|
ohne_eltern = [e for e in orphans if not e["parent"]]
|
||||||
|
if ohne_eltern:
|
||||||
|
for text in _wrap("Ausserdem liegen %d Datentraeger ohne Elternteil herum. "
|
||||||
|
"Die werden hier nicht angefasst - dafuer gibt es "
|
||||||
|
"'pvesnap-recovery cleanup --all'." % len(ohne_eltern), 74):
|
||||||
|
zeilen.append((text, curses.color_pair(C_DIM)))
|
||||||
|
|
||||||
|
taste = _pages(stdscr, zeilen, "Aufraeumen",
|
||||||
|
"j = aufraeumen | andere Taste = nichts tun")
|
||||||
|
if taste not in ("j", "J", "y", "Y"):
|
||||||
|
return
|
||||||
|
|
||||||
|
fortschritt = _Progress(stdscr, "Raeume auf")
|
||||||
|
bericht = []
|
||||||
|
if klone:
|
||||||
|
entfernt, gescheitert = remove_orphans(proxmox, klone, progress=fortschritt)
|
||||||
|
bericht.append("%d Datentraeger entfernt." % len(entfernt))
|
||||||
|
bericht += [" fehlgeschlagen: %s" % p for p in gescheitert]
|
||||||
|
if stale:
|
||||||
|
geloest, gescheitert = release_protections(proxmox, stale,
|
||||||
|
progress=fortschritt)
|
||||||
|
bericht.append("%s geloest." % _anzahl(len(geloest), "Schutzmarke",
|
||||||
|
"Schutzmarken"))
|
||||||
|
bericht += [" fehlgeschlagen: %s" % p for p in gescheitert]
|
||||||
|
_pages(stdscr, _plain(bericht), "Fertig")
|
||||||
|
|
||||||
|
|
||||||
def _browse_transfer(win, path, volume):
|
def _browse_transfer(win, path, volume):
|
||||||
"""Den Zwei-Fenster-Commander auf ein Transfer-Laufwerk loslassen.
|
"""Den Zwei-Fenster-Commander auf ein Transfer-Laufwerk loslassen.
|
||||||
|
|
||||||
@@ -1119,9 +1198,23 @@ def cmd_flatten(proxmox, args):
|
|||||||
|
|
||||||
|
|
||||||
def cmd_cleanup(proxmox, args):
|
def cmd_cleanup(proxmox, args):
|
||||||
|
"""Zwei Durchgaenge - sie finden verschiedene Dinge.
|
||||||
|
|
||||||
|
Der erste sucht Datentraeger ohne Gast. Der zweite geschuetzte Snapshots
|
||||||
|
ohne Klon: die findet der erste prinzipiell nicht, weil es dazu keinen
|
||||||
|
Datentraeger mehr gibt, nach dem er suchen koennte.
|
||||||
|
"""
|
||||||
|
schlecht = _cleanup_volumes(proxmox, args)
|
||||||
|
print()
|
||||||
|
schlecht += _cleanup_protections(proxmox, args)
|
||||||
|
return 1 if schlecht else 0
|
||||||
|
|
||||||
|
|
||||||
|
def _cleanup_volumes(proxmox, args):
|
||||||
|
"""Liegengebliebene Datentraeger. Liefert die Zahl der Fehlschlaege."""
|
||||||
orphans = find_orphans(proxmox)
|
orphans = find_orphans(proxmox)
|
||||||
if not orphans:
|
if not orphans:
|
||||||
print("Nichts liegengeblieben.")
|
print("Keine liegengebliebenen Datentraeger.")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
clones = [e for e in orphans if e["parent"]]
|
clones = [e for e in orphans if e["parent"]]
|
||||||
@@ -1158,6 +1251,39 @@ def cmd_cleanup(proxmox, args):
|
|||||||
return 1 if failed else 0
|
return 1 if failed else 0
|
||||||
|
|
||||||
|
|
||||||
|
def _anzahl(zahl, einzahl, mehrzahl):
|
||||||
|
return "%d %s" % (zahl, einzahl if zahl == 1 else mehrzahl)
|
||||||
|
|
||||||
|
|
||||||
|
def _cleanup_protections(proxmox, args):
|
||||||
|
"""Geschuetzte Snapshots ohne Klon. Liefert die Zahl der Fehlschlaege."""
|
||||||
|
print("Suche geschuetzte Snapshots ohne Klon ...")
|
||||||
|
stale = find_stale_protections(proxmox,
|
||||||
|
progress=lambda text: print(" " + text))
|
||||||
|
if not stale:
|
||||||
|
print("Keine gefunden.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
print("\nGeschuetzte Snapshots, an denen kein Klon mehr haengt:\n")
|
||||||
|
for entry in stale:
|
||||||
|
print(" %s@%s" % (entry["volid"], entry["snapshot"]))
|
||||||
|
|
||||||
|
print("\nAn diesen scheitert die Vorhaltezeit dauerhaft mit\n"
|
||||||
|
"\"snapshot is protected\". Der Schutz wird geloest - geloescht wird\n"
|
||||||
|
"dabei nichts, die Snapshots bleiben stehen.")
|
||||||
|
if not args.yes:
|
||||||
|
if _ask("\nSchutz loesen? [j/N] ") not in ("j", "y", "ja", "yes"):
|
||||||
|
print("Abgebrochen.")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
released, failed = release_protections(
|
||||||
|
proxmox, stale, progress=lambda text: print(" " + text))
|
||||||
|
print("\n%s geloest." % _anzahl(len(released), "Schutzmarke", "Schutzmarken"))
|
||||||
|
for problem in failed:
|
||||||
|
print(" fehlgeschlagen: %s" % problem, file=sys.stderr)
|
||||||
|
return 1 if failed else 0
|
||||||
|
|
||||||
|
|
||||||
def cmd_spice(proxmox, args):
|
def cmd_spice(proxmox, args):
|
||||||
instance = _find_instance(proxmox, args.vmid)
|
instance = _find_instance(proxmox, args.vmid)
|
||||||
text = spice_file(proxmox, instance)
|
text = spice_file(proxmox, instance)
|
||||||
|
|||||||