diff options
Diffstat (limited to 'vm-manager')
| -rw-r--r-- | vm-manager/README.md | 12 | ||||
| -rw-r--r-- | vm-manager/Virsh.qml | 45 | ||||
| -rw-r--r-- | vm-manager/VmPanel.qml | 14 |
3 files changed, 66 insertions, 5 deletions
diff --git a/vm-manager/README.md b/vm-manager/README.md index 8d817f9..bcf12ac 100644 --- a/vm-manager/README.md +++ b/vm-manager/README.md @@ -85,6 +85,18 @@ in a delete confirmation gives it up. ## Destructive actions +`Discard saved state` appears on a shut-off VM only when one actually exists, +and runs `virsh managedsave-remove`. A VM that was saved rather than shut down +restores that memory image on the next `start`, and when the image cannot be +restored the start fails every time with a QEMU `migrate-incoming` error while +the panel shows an ordinary `shut off`. Discarding it deletes the memory image +and nothing else, so the next start is a cold boot and the disk is untouched. +That is why it takes a confirmation but not a typed name. + +Detection is `virsh dominfo`, grepped for `Managed save: yes`, once per VM on +every list refresh. `domstats` does not carry it and `virsh list --name` drops +the column that would. + `Reset`, `Force stop`, snapshot `Revert` and snapshot `Delete` each take one confirmation click. `Delete VM` requires the machine's name to be typed, because it runs `virsh undefine --remove-all-storage`, which erases the disk diff --git a/vm-manager/Virsh.qml b/vm-manager/Virsh.qml index 309781c..14bd368 100644 --- a/vm-manager/Virsh.qml +++ b/vm-manager/Virsh.qml @@ -21,7 +21,7 @@ import QtQuick Singleton { id: root - // name -> { state, cpu, memUsed, memTotal, fsUsed, fsTotal, ip, vcpus, agent } + // name -> { state, cpu, memUsed, memTotal, fsUsed, fsTotal, ip, vcpus, agent, saved } property var vms: ({}) property var names: [] property var snapshots: ({}) // name -> [{ name, created, state, current }] @@ -33,7 +33,8 @@ Singleton { function _vm(name) { return vms[name] ?? { state: "unknown", cpu: -1, memUsed: -1, memTotal: -1, - fsUsed: -1, fsTotal: -1, ip: "", vcpus: 0, agent: false }; + fsUsed: -1, fsTotal: -1, ip: "", vcpus: 0, agent: false, + saved: false }; } function _set(name, fields) { @@ -52,6 +53,7 @@ Singleton { const found = text.trim().split("\n").map(s => s.trim()).filter(s => s.length); root.names = found; for (const n of found) if (!(n in root.vms)) root._set(n, {}); + root._pollSaved(); root.refresh(); } } @@ -191,6 +193,44 @@ Singleton { onExited: code => { if (code !== 0) { root._set(vm, { ip: "" }); root._nextAgent(); } } } + // --- managed save ---------------------------------------------------- + // + // A shut-off VM can still carry a saved memory image, and `virsh start` + // then restores it rather than booting. When that image cannot be + // restored the start fails every time with a QEMU migrate-incoming + // error, and nothing in the panel said why. `domstats` does not report + // it: the shut-off reason reads "failed", from the failed start, not + // from the save. `dominfo` is the only cheap source, so it is polled per + // VM the way the agent rows are. + + property var _savedQueue: [] + + function _pollSaved() { + _savedQueue = names.slice(); + _nextSaved(); + } + + function _nextSaved() { + if (_savedQueue.length === 0) return; + const vm = _savedQueue[0]; + _savedQueue = _savedQueue.slice(1); + savedProc.vm = vm; + savedProc.command = ["virsh", "dominfo", vm]; + savedProc.running = true; + } + + Process { + id: savedProc + property string vm: "" + stdout: StdioCollector { + onStreamFinished: { + root._set(savedProc.vm, { saved: /^Managed save:\s+yes$/m.test(text) }); + root._nextSaved(); + } + } + onExited: code => { if (code !== 0) { root._set(vm, { saved: false }); root._nextSaved(); } } + } + // --- snapshots ------------------------------------------------------- Process { @@ -227,6 +267,7 @@ Singleton { destroy: v => ["virsh", "destroy", v], suspend: v => ["virsh", "suspend", v], resume: v => ["virsh", "resume", v], + discardsave: v => ["virsh", "managedsave-remove", v], }) function act(vm, action) { diff --git a/vm-manager/VmPanel.qml b/vm-manager/VmPanel.qml index cf224d6..5960a1a 100644 --- a/vm-manager/VmPanel.qml +++ b/vm-manager/VmPanel.qml @@ -62,16 +62,21 @@ Scope { // Which verbs make sense in the current state, mirroring the states the // old rofi script switched on. - function actionsFor(s) { + function actionsFor(s, saved) { if (s === "running") return [["shutdown", "Shutdown"], ["reboot", "Reboot"], ["suspend", "Suspend"], ["reset", "Reset"], ["destroy", "Force stop"]]; if (s === "paused" || s === "suspended") return [["resume", "Resume"], ["shutdown", "Shutdown"], ["destroy", "Force stop"]]; + // Only worth offering when a saved image actually exists: without one + // managedsave-remove fails, and the button would be noise on every + // other VM. + if (saved) + return [["start", "Start"], ["discardsave", "Discard saved state"]]; return [["start", "Start"]]; } - function isDestructive(a) { return a === "reset" || a === "destroy"; } + function isDestructive(a) { return a === "reset" || a === "destroy" || a === "discardsave"; } function run(vm, action) { if (isDestructive(action)) root.confirming = { kind: action, vm: vm, snap: "" }; @@ -265,6 +270,7 @@ Scope { sourceComponent: detail property string vmName: modelData property string vmState: vm.state ?? "" + property bool vmSaved: vm.saved ?? false } } } @@ -282,6 +288,7 @@ Scope { readonly property string vmName: parent.vmName readonly property string vmState: parent.vmState + readonly property bool vmSaved: parent.vmSaved Rectangle { width: parent.width; height: 1; color: Qt.alpha(Theme.text, 0.08) } @@ -299,7 +306,7 @@ Scope { spacing: 8 Repeater { - model: root.actionsFor(vmState) + model: root.actionsFor(vmState, vmSaved) Button { required property var modelData text: modelData[1] @@ -391,6 +398,7 @@ Scope { if (c.kind === "revert") return `Revert ${c.vm} to "${c.snap}"? Changes since that snapshot are lost.`; if (c.kind === "snapdelete") return `Delete snapshot "${c.snap}"?`; if (c.kind === "destroy") return `Force stop ${c.vm}? This is a power cut, not a shutdown.`; + if (c.kind === "discardsave") return `Discard the saved state of ${c.vm}? Its memory image is deleted and the next start boots cold. The disk is untouched.`; if (c.kind === "reset") return `Reset ${c.vm}? This is a hard reset, not a reboot.`; return ""; } |
