Skip to content

Commit

Permalink
WIP - only ask for memory save directory, not full filename.
Browse files Browse the repository at this point in the history
Get default from existing snapshots as well.
  • Loading branch information
mvollmer committed Jul 18, 2024
1 parent 1e3ffd5 commit 245caff
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 21 deletions.
58 changes: 38 additions & 20 deletions src/components/vm/snapshots/vmSnapshotsCreateModal.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -64,37 +64,48 @@ 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" &&
d.device.source.file);
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 (
<FormGroup id="snapshot-create-dialog-memory-path" label={_("Memory file")}>
<FormGroup id="snapshot-create-dialog-memory-location" label={_("Memory save location")}>
<FileAutoComplete
onChange={value => onValueChanged("memoryPath", value)}
onChange={value => onValueChanged("memoryLocation", value)}
superuser="try"
isOptionCreatable
value={memoryPath} />
<FormHelper helperTextInvalid={validationError} />
value={memoryLocation} />
<FormHelper helperTextInvalid={validationError}
helperText={_("XXX GB available at this location. Up to YYY GB might be needed to save the memory state of the machine.")} />
</FormGroup>
);
};
Expand All @@ -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,
};

Expand All @@ -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 = {};

Expand All @@ -139,26 +150,33 @@ 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;
}

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
Expand All @@ -178,16 +196,16 @@ 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 = (
<Form onSubmit={e => e.preventDefault()} isHorizontal>
<NameRow name={name} validationError={validationError.name} onValueChanged={this.onValueChanged} />
<DescriptionRow description={description} onValueChanged={this.onValueChanged} />
{isExternal && vm.state === 'running' &&
<MemoryPathRow memoryPath={memoryPath} onValueChanged={this.onValueChanged}
validationError={validationError.memory} />}
<MemoryLocationRow memoryLocation={memoryLocation} onValueChanged={this.onValueChanged}
validationError={validationError.memory} />}
</Form>
);

Expand Down
4 changes: 3 additions & 1 deletion src/libvirt-xml-parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down

0 comments on commit 245caff

Please sign in to comment.