From 245caff4a01193b877361a0bf62e3c9b3024fc9f Mon Sep 17 00:00:00 2001 From: Marius Vollmer Date: Thu, 18 Jul 2024 16:42:29 +0300 Subject: [PATCH] WIP - only ask for memory save directory, not full filename. Get default from existing snapshots as well. --- .../vm/snapshots/vmSnapshotsCreateModal.jsx | 58 ++++++++++++------- src/libvirt-xml-parse.js | 4 +- 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/src/components/vm/snapshots/vmSnapshotsCreateModal.jsx b/src/components/vm/snapshots/vmSnapshotsCreateModal.jsx index 8214c3aac..e896e3fd4 100644 --- a/src/components/vm/snapshots/vmSnapshotsCreateModal.jsx +++ b/src/components/vm/snapshots/vmSnapshotsCreateModal.jsx @@ -64,10 +64,21 @@ const DescriptionRow = ({ onValueChanged, description }) => { ); }; -function getDefaultMemoryPath(vm, snapName) { +function getDefaultMemoryLocation(vm) { // Choosing a default path where memory snapshot should be stored might be tricky. Ideally we want // to store it in the same directory where the primary disk (the disk which is first booted) is stored // If howver no such disk can be found, we should fallback to libvirt's default /var/lib/libvirt + + // If we find an existing external snapshot, use it's memory path + // as the default. + + for (const s of vm.snapshots.sort((a, b) => b.creationTime - a.creationTime)) { + if (s.memoryPath) { + // XXX - robustify + return s.memoryPath.substr(0, s.memoryPath.lastIndexOf("/") + 1); + } + } + const devices = getSortedBootOrderDevices(vm).filter(d => d.bootOrder && d.device.device === "disk" && d.device.type === "file" && @@ -75,26 +86,26 @@ function getDefaultMemoryPath(vm, snapName) { if (devices.length > 0) { const primaryDiskPath = devices[0].device.source.file; const directory = primaryDiskPath.substring(0, primaryDiskPath.lastIndexOf("/") + 1); - return directory + snapName; + return directory; } else { if (vm.connectionName === LIBVIRT_SYSTEM_CONNECTION) - return "/var/lib/libvirt/memory/" + snapName; + return "/var/lib/libvirt/memory/"; else if (current_user) - return current_user.home + "/.local/share/libvirt/memory/" + snapName; + return current_user.home + "/.local/share/libvirt/memory/"; } return ""; } -const MemoryPathRow = ({ onValueChanged, memoryPath, validationError }) => { +const MemoryLocationRow = ({ onValueChanged, memoryLocation, validationError }) => { return ( - + onValueChanged("memoryPath", value)} + onChange={value => onValueChanged("memoryLocation", value)} superuser="try" - isOptionCreatable - value={memoryPath} /> - + value={memoryLocation} /> + ); }; @@ -107,11 +118,11 @@ export class CreateSnapshotModal extends React.Component { // cut off seconds, subseconds, and timezone const now = new Date().toISOString() .replace(/:[^:]*$/, ''); - const snapName = props.vm.name + '_' + now; + const snapName = now; this.state = { name: snapName, description: "", - memoryPath: getDefaultMemoryPath(props.vm, snapName), + memoryLocation: getDefaultMemoryLocation(props.vm), inProgress: false, }; @@ -130,7 +141,7 @@ export class CreateSnapshotModal extends React.Component { } onValidate() { - const { name, memoryPath } = this.state; + const { name, memoryLocation } = this.state; const { vm, isExternal } = this.props; const validationError = {}; @@ -139,8 +150,8 @@ export class CreateSnapshotModal extends React.Component { else if (!name) validationError.name = _("Name can not be empty"); - if (isExternal && vm.state === "running" && !memoryPath) - validationError.memory = _("Memory file can not be empty"); + if (isExternal && vm.state === "running" && !memoryLocation) + validationError.memory = _("Memory save location can not be empty"); return validationError; } @@ -148,17 +159,24 @@ export class CreateSnapshotModal extends React.Component { onCreate() { const Dialogs = this.context; const { vm, isExternal } = this.props; - const { name, description, memoryPath } = this.state; + const { name, description, memoryLocation } = this.state; const validationError = this.onValidate(); if (!Object.keys(validationError).length) { this.setState({ inProgress: true }); + let mpath = null; + if (isExternal && vm.state === "running" && memoryLocation) { + mpath = memoryLocation; + if (mpath[mpath.length-1] != "/") + mpath = mpath + "/"; + mpath = mpath + vm.name + "." + name + ".save"; + } snapshotCreate({ vm, name, description, isExternal, - memoryPath: isExternal && vm.state === "running" && memoryPath, + memoryPath: mpath, }) .then(() => { // VM Snapshots do not trigger any events so we have to refresh them manually @@ -178,7 +196,7 @@ export class CreateSnapshotModal extends React.Component { render() { const Dialogs = this.context; const { idPrefix, isExternal, vm } = this.props; - const { name, description, memoryPath } = this.state; + const { name, description, memoryLocation } = this.state; const validationError = this.onValidate(); const body = ( @@ -186,8 +204,8 @@ export class CreateSnapshotModal extends React.Component { {isExternal && vm.state === 'running' && - } + } ); diff --git a/src/libvirt-xml-parse.js b/src/libvirt-xml-parse.js index 74492abc0..4da8bd7a1 100644 --- a/src/libvirt-xml-parse.js +++ b/src/libvirt-xml-parse.js @@ -216,14 +216,16 @@ export function parseDomainSnapshotDumpxml(snapshot) { const nameElem = getSingleOptionalElem(snapElem, 'name'); const descElem = getSingleOptionalElem(snapElem, 'description'); const parentElem = getSingleOptionalElem(snapElem, 'parent'); + const memElem = getSingleOptionalElem(snapElem, 'memory'); const name = nameElem?.childNodes[0].nodeValue; const description = descElem?.childNodes[0].nodeValue; const parentName = parentElem?.getElementsByTagName("name")[0].childNodes[0].nodeValue; const state = snapElem.getElementsByTagName("state")[0].childNodes[0].nodeValue; const creationTime = snapElem.getElementsByTagName("creationTime")[0].childNodes[0].nodeValue; + const memoryPath = memElem?.getAttribute("file"); - return { name, description, state, creationTime, parentName }; + return { name, description, state, creationTime, parentName, memoryPath }; } export function parseDomainDumpxml(connectionName, domXml, objPath) {