feat(xo-server,xo-web/snapshot): ability to snapshot the VM memory (#3812)

Fixes #3795
This commit is contained in:
badrAZ 2019-01-08 16:10:00 +01:00 committed by Pierre Donias
parent b459f74a8c
commit b8a3d00343
6 changed files with 73 additions and 15 deletions

View File

@ -7,6 +7,7 @@
- [Backup NG] Restore logs moved to restore tab [#3772](https://github.com/vatesfr/xen-orchestra/issues/3772) (PR [#3802](https://github.com/vatesfr/xen-orchestra/pull/3802))
- [Remotes] New SMB implementation that provides better stability and performance [#2257](https://github.com/vatesfr/xen-orchestra/issues/2257) (PR [#3708](https://github.com/vatesfr/xen-orchestra/pull/3708))
- [VM/advanced] ACL management from VM view [#3040](https://github.com/vatesfr/xen-orchestra/issues/3727) (PR [#3040](https://github.com/vatesfr/xen-orchestra/pull/3774))
- [VM / snapshots] Ability to save the VM memory [#3795](https://github.com/vatesfr/xen-orchestra/issues/3795) (PR [#3812](https://github.com/vatesfr/xen-orchestra/pull/3812))
### Bug fixes

View File

@ -20,12 +20,19 @@ export default {
// https://xapi-project.github.io/xen-api/classes/vm.html#checkpoint
async checkpointVm(vmId, nameLabel) {
const vm = this.getObject(vmId)
const ref = await this.callAsync(
'VM.checkpoint',
vm.$ref,
nameLabel != null ? nameLabel : vm.name_label
).then(extractOpaqueRef)
return this.barrier(ref)
try {
const ref = await this.callAsync(
'VM.checkpoint',
vm.$ref,
nameLabel != null ? nameLabel : vm.name_label
).then(extractOpaqueRef)
return this.barrier(ref)
} catch (error) {
if (error.code === 'VM_BAD_POWER_STATE') {
return this._snapshotVm(vm, nameLabel)
}
throw error
}
},
// TODO: clean up on error.

View File

@ -1001,6 +1001,8 @@ const messages = {
// ----- VM snapshot tab -----
noSnapshots: 'No snapshots',
newSnapshotWithMemory: 'New snapshot with memory',
snapshotMemorySaved: 'memory saved',
snapshotCreateButton: 'New snapshot',
tipCreateSnapshotLabel: 'Just click on the snapshot button to create one!',
revertSnapshot: 'Revert VM to this snapshot',
@ -1477,9 +1479,8 @@ const messages = {
restartVmsModalTitle: 'Restart VM{vms, plural, one {} other {s}}',
restartVmsModalMessage:
'Are you sure you want to restart {vms, number} VM{vms, plural, one {} other {s}}?',
snapshotSaveMemory: 'save memory',
snapshotVmsModalTitle: 'Snapshot VM{vms, plural, one {} other {s}}',
snapshotVmsModalMessage:
'Are you sure you want to snapshot {vms, number} VM{vms, plural, one {} other {s}}?',
deleteVmsModalTitle: 'Delete VM{vms, plural, one {} other {s}}',
deleteVmsModalMessage:
'Are you sure you want to delete {vms, number} VM{vms, plural, one {} other {s}}? ALL VM DISKS WILL BE REMOVED',

View File

@ -1116,13 +1116,19 @@ export const deleteTemplates = templates =>
}, noop)
}, noop)
export const snapshotVm = vm => _call('vm.snapshot', { id: resolveId(vm) })
export const snapshotVm = (vm, saveMemory) =>
_call('vm.snapshot', { id: resolveId(vm), saveMemory })
import SnapshotVmModalBody from './snapshot-vm-modal' // eslint-disable-line import/first
export const snapshotVms = vms =>
confirm({
icon: 'memory',
title: _('snapshotVmsModalTitle', { vms: vms.length }),
body: _('snapshotVmsModalMessage', { vms: vms.length }),
}).then(() => map(vms, vmId => snapshotVm({ id: vmId })), noop)
body: <SnapshotVmModalBody />,
}).then(
saveMemory => Promise.all(map(vms, vm => snapshotVm(vm, saveMemory))),
noop
)
export const deleteSnapshot = vm =>
confirm({

View File

@ -0,0 +1,23 @@
import Component from 'base-component'
import React from 'react'
import _ from '../../intl'
export default class SnapshotVmModalBody extends Component {
get value() {
return this.state.saveMemory
}
render() {
return (
<label>
<input
type='checkbox'
onChange={this.linkState('saveMemory')}
checked={this.state.saveMemory}
/>{' '}
{_('snapshotSaveMemory')}
</label>
)
}
}

View File

@ -48,10 +48,18 @@ const COLUMNS = [
},
{
itemRenderer: snapshot => (
<Text
onChange={value => editVm(snapshot, { name_label: value })}
value={snapshot.name_label}
/>
<div>
<Text
onChange={value => editVm(snapshot, { name_label: value })}
value={snapshot.name_label}
/>{' '}
{/* checkpoint snapshots are in a Suspended state */}
{snapshot.power_state === 'Suspended' && (
<Tooltip content={_('snapshotMemorySaved')}>
<Icon icon='memory' color='text-success' />
</Tooltip>
)}
</div>
),
name: _('snapshotName'),
sortCriteria: _ => _.name_label,
@ -107,6 +115,8 @@ const INDIVIDUAL_ACTIONS = [
},
]
const _snapshotVmWithMemory = vm => snapshotVm(vm, true)
@connectStore(() => ({
snapshots: createGetObjectsOfType('VM-snapshot')
.pick((_, props) => props.vm.snapshots)
@ -119,6 +129,16 @@ export default class TabSnapshot extends Component {
<Container>
<Row>
<Col className='text-xs-right'>
{vm.power_state !== 'Halted' && (
<TabButton
btnStyle='primary'
handler={_snapshotVmWithMemory}
handlerParam={vm}
icon='memory'
labelId='newSnapshotWithMemory'
pending={includes(vm.current_operations, 'checkpoint')}
/>
)}
<TabButton
btnStyle='primary'
handler={snapshotVm}