Transfer-Laufwerke im laufenden Betrieb wechseln
Auswerfen und wieder einklinken, ohne die Maschine anzufassen - damit laesst
sich mehrfach nachladen und abholen, statt nur einmal am Ende.
eject_transfer() zieht die Platte ab, danach gehoert sie dem Host
insert_transfer() gibt sie zurueck in denselben Steckplatz
transfer_state() was gerade wirklich steckt
In der Detailansicht auf Taste e; bei mehreren Laufwerken mit Auswahl. Vor dem
Auswerfen wird nachgefragt, mit dem Hinweis, im Gast vorher auszuhaengen -
Proxmox meldet das Geraet zwar ab, aber ein Dateisystem, das noch beschrieben
wird, nimmt das uebel. Schlaegt es fehl, weil der Gast es festhaelt, steht das
auch so in der Meldung.
Beim Container gibt es nichts zu wechseln und das wird auch so gesagt: dort
ist das Verzeichnis durchgereicht, Host und Gast sehen dieselben Dateien
ohnehin gleichzeitig.
Dabei ein Fehler aufgefallen, den erst der Test zeigte: bei einer Maschine mit
geladenem Arbeitsspeicher steckte das Transfer-Laufwerk in der kalten
Konfiguration - der Gast haette es nie bemerkt. Dieselbe Falle wie beim
Austauschlaufwerk, dort schon behoben, hier vergessen. Beide gehen jetzt durch
dieselbe Nachsteck-Logik.
Am laufenden System durchgespielt, mit RAM-Zustand:
Start "Stecke Transfer-Laufwerk 'hotswap' als scsi2 an"
auswerfen eingesteckt=False, Laufwerk wieder frei
befuellen runde2.txt vom Host, VM lief weiter
einklinken scsi2: /dev/loop0,backup=0,size=1G
Ende beide Dateien da, VM durchgehend running
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
2b895e6f4f
commit
0a92ec0984
+141
-12
@@ -955,16 +955,25 @@ def create(proxmox, plan_, progress=None):
|
||||
if guest.type == "lxc":
|
||||
# Beim Container wird der Mountpunkt des Hosts durchgereicht.
|
||||
# Ein Bind-Mount ist dasselbe Dateisystem an zwei Stellen, kein
|
||||
# zweites Einhaengen - das ist unbedenklich.
|
||||
# zweites Einhaengen - Host und Gast koennen beide arbeiten.
|
||||
step("Reiche Transfer-Laufwerk %r durch" % volume.name)
|
||||
path = transfer.host_mount(volume.name)
|
||||
slot = _free_mp_slot(new_config)
|
||||
new_config[slot] = "%s,mp=/mnt/%s" % (path, volume.name)
|
||||
value = "%s,mp=/mnt/%s" % (path, volume.name)
|
||||
new_config[slot] = value
|
||||
attached.append({"name": volume.name, "key": slot, "drive": value,
|
||||
"kind": "bind", "pending": False})
|
||||
else:
|
||||
step("Haenge Transfer-Laufwerk %r an" % volume.name)
|
||||
slot = _free_disk_slot(new_config)
|
||||
new_config[slot] = transfer.drive_string(volume.name)
|
||||
attached.append(volume.name)
|
||||
value = transfer.drive_string(volume.name)
|
||||
# Mit geladenem Arbeitsspeicher wacht der Gast in einem Zustand
|
||||
# auf, in dem es die Platte nicht gab - dann muss sie erst nach
|
||||
# dem Fortsetzen dazukommen, sonst bemerkt er sie nie.
|
||||
if not plan_.resume:
|
||||
new_config[slot] = value
|
||||
attached.append({"name": volume.name, "key": slot, "drive": value,
|
||||
"kind": "disk", "pending": bool(plan_.resume)})
|
||||
|
||||
# -- restliche Anpassungen -----------------------------------------
|
||||
if spec.memory:
|
||||
@@ -1071,10 +1080,32 @@ def start(proxmox, instance, progress=None):
|
||||
|
||||
|
||||
def _attach_pending(proxmox, instance, step=None):
|
||||
"""Das Austauschlaufwerk an die schon laufende Maschine anstecken."""
|
||||
"""Was erst nach dem Fortsetzen dazukommen darf, jetzt anstecken."""
|
||||
notes = []
|
||||
for entry in _transfer_entries(instance):
|
||||
if not entry.get("pending"):
|
||||
continue
|
||||
if step:
|
||||
step("Stecke Transfer-Laufwerk %r als %s an"
|
||||
% (entry["name"], entry["key"]))
|
||||
try:
|
||||
proxmox.set_guest_config(instance.guest, {entry["key"]: entry["drive"]})
|
||||
config = proxmox.guest_config(instance.guest)
|
||||
except ProxmoxError as exc:
|
||||
notes.append("Transfer-Laufwerk %r liess sich nicht anstecken: %s"
|
||||
% (entry["name"], exc))
|
||||
continue
|
||||
if entry["key"] not in config:
|
||||
notes.append("Transfer-Laufwerk %r ist eingetragen, aber nicht "
|
||||
"angesteckt - es erscheint erst nach einem Neustart."
|
||||
% entry["name"])
|
||||
else:
|
||||
entry["pending"] = False
|
||||
_registry_add(instance)
|
||||
|
||||
exchange = instance.exchange or {}
|
||||
if not exchange.get("pending"):
|
||||
return ""
|
||||
return " ".join(notes)
|
||||
key, drive = exchange.get("key"), exchange.get("drive")
|
||||
if not key or not drive:
|
||||
return ""
|
||||
@@ -1084,16 +1115,114 @@ def _attach_pending(proxmox, instance, step=None):
|
||||
proxmox.set_guest_config(instance.guest, {key: drive})
|
||||
config = proxmox.guest_config(instance.guest)
|
||||
except ProxmoxError as exc:
|
||||
return "Das Austauschlaufwerk liess sich nicht anstecken: %s" % exc
|
||||
notes.append("Das Austauschlaufwerk liess sich nicht anstecken: %s" % exc)
|
||||
return " ".join(notes)
|
||||
|
||||
if key not in config:
|
||||
# Proxmox hat es nur vorgemerkt - im Gast taucht es dann nicht auf.
|
||||
return ("Das Austauschlaufwerk ist eingetragen, aber nicht angesteckt "
|
||||
"worden (Hotplug fuer Platten ist bei dieser Maschine aus). "
|
||||
"Es erscheint erst nach einem Neustart des Gastes.")
|
||||
notes.append("Das Austauschlaufwerk ist eingetragen, aber nicht "
|
||||
"angesteckt worden (Hotplug fuer Platten ist bei dieser "
|
||||
"Maschine aus). Es erscheint erst nach einem Neustart.")
|
||||
else:
|
||||
exchange["pending"] = False
|
||||
_registry_add(instance)
|
||||
return ""
|
||||
return " ".join(notes)
|
||||
|
||||
|
||||
def _transfer_entries(instance):
|
||||
"""Die Transfer-Laufwerke einer Instanz als Datensaetze.
|
||||
|
||||
Aeltere Merklisten enthalten nur Namen - die werden hier mitgelesen.
|
||||
"""
|
||||
entries = []
|
||||
for entry in instance.transfers or []:
|
||||
if isinstance(entry, dict):
|
||||
entries.append(entry)
|
||||
else:
|
||||
entries.append({"name": str(entry), "key": "", "drive": "",
|
||||
"kind": "disk", "pending": False})
|
||||
return entries
|
||||
|
||||
|
||||
def transfer_state(proxmox, instance):
|
||||
"""[(Datensatz, eingesteckt?)] - was gerade in der Maschine steckt."""
|
||||
try:
|
||||
config = proxmox.guest_config(instance.guest)
|
||||
except ProxmoxError:
|
||||
config = {}
|
||||
return [(entry, bool(entry.get("key") and entry["key"] in config))
|
||||
for entry in _transfer_entries(instance)]
|
||||
|
||||
|
||||
def eject_transfer(proxmox, instance, name, progress=None):
|
||||
"""Ein Transfer-Laufwerk bei laufender Maschine abziehen.
|
||||
|
||||
Danach gehoert es wieder dem Host: einhaengen, befuellen, auslesen - und
|
||||
mit insert_transfer() wieder hineingeben, ohne die Maschine anzufassen.
|
||||
|
||||
Wichtig: im Gast vorher aushaengen. Proxmox meldet das Geraet ab, aber ein
|
||||
Dateisystem, das noch beschrieben wird, nimmt das uebel.
|
||||
"""
|
||||
def step(text):
|
||||
log.info("%s", text)
|
||||
if progress:
|
||||
progress(text)
|
||||
|
||||
entry = next((e for e in _transfer_entries(instance) if e["name"] == name), None)
|
||||
if entry is None:
|
||||
raise RecoveryError("%r haengt nicht an %s." % (name, instance.label))
|
||||
if entry.get("kind") == "bind":
|
||||
raise RecoveryError(
|
||||
"Bei Containern gibt es nichts auszuwerfen: das Verzeichnis ist "
|
||||
"durchgereicht, Host und Gast sehen dieselben Dateien sofort.")
|
||||
if not entry.get("key"):
|
||||
raise RecoveryError("Zu %r ist kein Steckplatz vermerkt." % name)
|
||||
|
||||
step("Werfe %r aus (%s)" % (name, entry["key"]))
|
||||
try:
|
||||
proxmox.set_guest_config(instance.guest, {"delete": entry["key"]})
|
||||
except ProxmoxError as exc:
|
||||
raise RecoveryError(
|
||||
"%r liess sich nicht abziehen: %s\n"
|
||||
"Meist haelt der Gast es noch - dort erst aushaengen "
|
||||
"(Linux: umount, Windows: Auswerfen im Explorer)." % (name, exc))
|
||||
|
||||
entry["pending"] = True
|
||||
_registry_add(instance)
|
||||
transfer.release(name)
|
||||
step("%r gehoert jetzt wieder dem Host" % name)
|
||||
return True
|
||||
|
||||
|
||||
def insert_transfer(proxmox, instance, name, progress=None):
|
||||
"""Ein zuvor ausgeworfenes Transfer-Laufwerk wieder einklinken."""
|
||||
def step(text):
|
||||
log.info("%s", text)
|
||||
if progress:
|
||||
progress(text)
|
||||
|
||||
entry = next((e for e in _transfer_entries(instance) if e["name"] == name), None)
|
||||
if entry is None:
|
||||
raise RecoveryError("%r gehoert nicht zu %s." % (name, instance.label))
|
||||
if entry.get("kind") == "bind":
|
||||
raise RecoveryError("Bei Containern ist das Verzeichnis dauerhaft "
|
||||
"durchgereicht - nichts einzuklinken.")
|
||||
|
||||
volume = transfer.get(name)
|
||||
if volume.mountpoint:
|
||||
step("%r ist noch auf dem Host eingehaengt - haenge aus" % name)
|
||||
transfer.host_umount(name)
|
||||
|
||||
drive = transfer.drive_string(name)
|
||||
entry["drive"] = drive
|
||||
step("Klinke %r wieder ein (%s)" % (name, entry["key"]))
|
||||
try:
|
||||
proxmox.set_guest_config(instance.guest, {entry["key"]: drive})
|
||||
except ProxmoxError as exc:
|
||||
raise RecoveryError("%r liess sich nicht einklinken: %s" % (name, exc))
|
||||
entry["pending"] = False
|
||||
_registry_add(instance)
|
||||
return True
|
||||
|
||||
|
||||
def _default_name(guest, plan_):
|
||||
@@ -1495,7 +1624,7 @@ def destroy(proxmox, instance, progress=None, keep_snapshot_protection=False):
|
||||
except RecoveryError as exc:
|
||||
log.warning("Schutz von %s@%s blieb: %s", volid, snapname, exc)
|
||||
|
||||
for name in instance.transfers or []:
|
||||
for name in [e["name"] for e in _transfer_entries(instance)]:
|
||||
step("Gebe Transfer-Laufwerk %r wieder frei" % name)
|
||||
try:
|
||||
transfer.host_umount(name)
|
||||
|
||||
+51
-1
@@ -27,6 +27,7 @@ from .curses_util import (C_DIM, C_FOOTER, C_HEADER, C_MARK, C_OK, C_SEL, C_WARN
|
||||
keybar, message, prompt, put, read_key)
|
||||
from .proxmox import Proxmox, ProxmoxError, pvesh_available
|
||||
from .recovery import (EXCHANGE_GUEST_PATH, RecoveryError, Spec, create,
|
||||
eject_transfer, insert_transfer, transfer_state,
|
||||
destroy, find_orphans, flatten, flatten_cost, flatten_hint,
|
||||
human_bytes,
|
||||
linked_volumes, list_instances, plan, pull, release,
|
||||
@@ -476,6 +477,24 @@ def _details(stdscr, proxmox, instance):
|
||||
"ersten Start lief die Maschine genau weiter.",
|
||||
curses.color_pair(C_DIM)))
|
||||
|
||||
steckplaetze = transfer_state(proxmox, instance)
|
||||
if steckplaetze:
|
||||
lines.append(("", 0))
|
||||
lines.append(("Transfer-Laufwerke:", 0))
|
||||
for entry, drin in steckplaetze:
|
||||
if entry.get("kind") == "bind":
|
||||
zustand = "durchgereicht (Host und Gast gleichzeitig)"
|
||||
farbe = C_OK
|
||||
else:
|
||||
zustand = ("eingesteckt als %s" % entry["key"]) if drin \
|
||||
else "ausgeworfen - gehoert dem Host"
|
||||
farbe = C_OK if drin else C_MARK
|
||||
lines.append((" %-14s %s" % (entry["name"], zustand),
|
||||
curses.color_pair(farbe)))
|
||||
if any(e.get("kind") != "bind" for e, _ in steckplaetze):
|
||||
lines.append((" e = auswerfen / einklinken, im laufenden "
|
||||
"Betrieb", curses.color_pair(C_DIM)))
|
||||
|
||||
try:
|
||||
linked = linked_volumes(proxmox, instance)
|
||||
except (RecoveryError, ProxmoxError):
|
||||
@@ -493,7 +512,7 @@ def _details(stdscr, proxmox, instance):
|
||||
lines.append(("Datentraeger: eigenstaendig - haengt an keinem Snapshot",
|
||||
curses.color_pair(C_OK)))
|
||||
|
||||
footer = ("s Starten | h Herunterfahren | a Austausch | f Loesen | "
|
||||
footer = ("s Start | h Stop | e Auswerfen | a Austausch | f Loesen | "
|
||||
"x Verwerfen | q zurueck")
|
||||
key = _pages(stdscr, lines, "Wiederherstellung %s" % instance.label, footer)
|
||||
|
||||
@@ -507,6 +526,8 @@ def _details(stdscr, proxmox, instance):
|
||||
lambda: proxmox.shutdown_guest(instance.guest))
|
||||
elif key in ("a", "A"):
|
||||
_pull(stdscr, proxmox, instance)
|
||||
elif key in ("e", "E"):
|
||||
_swap(stdscr, proxmox, instance, steckplaetze)
|
||||
elif key in ("f", "F"):
|
||||
_flatten(stdscr, proxmox, instance, linked)
|
||||
elif key in ("x", "X"):
|
||||
@@ -545,6 +566,35 @@ def _pull(stdscr, proxmox, instance):
|
||||
]), "Austauschlaufwerk")
|
||||
|
||||
|
||||
def _swap(stdscr, proxmox, instance, steckplaetze):
|
||||
"""Transfer-Laufwerk im laufenden Betrieb abziehen oder wieder einklinken."""
|
||||
wechselbar = [(e, drin) for e, drin in steckplaetze if e.get("kind") != "bind"]
|
||||
if not wechselbar:
|
||||
message(stdscr, "Hier gibt es nichts zu wechseln.", error=True)
|
||||
return
|
||||
|
||||
if len(wechselbar) == 1:
|
||||
entry, drin = wechselbar[0]
|
||||
else:
|
||||
gewaehlt = choose(stdscr, "Welches Laufwerk?",
|
||||
[((e, d), "%-14s %s" % (e["name"],
|
||||
"eingesteckt" if d else "ausgeworfen"))
|
||||
for e, d in wechselbar])
|
||||
if gewaehlt is None:
|
||||
return
|
||||
entry, drin = gewaehlt
|
||||
|
||||
name = entry["name"]
|
||||
if drin:
|
||||
if not confirm(stdscr, "%r auswerfen? Im Gast vorher aushaengen!" % name):
|
||||
return
|
||||
_do(stdscr, "Werfe %r aus ..." % name,
|
||||
lambda: eject_transfer(proxmox, instance, name))
|
||||
else:
|
||||
_do(stdscr, "Klinke %r ein ..." % name,
|
||||
lambda: insert_transfer(proxmox, instance, name))
|
||||
|
||||
|
||||
def _flatten(stdscr, proxmox, instance, linked):
|
||||
if not linked:
|
||||
message(stdscr, "%s haengt an keinem Snapshot mehr." % instance.label)
|
||||
|
||||
Reference in New Issue
Block a user