feat({xo-server,xo-web}/VM): pause/unpause (#3731)

Fixes #3727
This commit is contained in:
Pierre Donias
2018-11-29 11:51:42 +01:00
committed by GitHub
parent 5271a5c984
commit 08583c06ef
11 changed files with 113 additions and 14 deletions

View File

@@ -7,6 +7,7 @@
- [Perf alert] Ability to trigger an alarm if a host/VM/SR usage value is below the threshold [#3612](https://github.com/vatesfr/xen-orchestra/issues/3612) (PR [#3675](https://github.com/vatesfr/xen-orchestra/pull/3675))
- [Home/VMs] Display pool's name [#2226](https://github.com/vatesfr/xen-orchestra/issues/2226) (PR [#3709](https://github.com/vatesfr/xen-orchestra/pull/3709))
- [Servers] Prevent new connection if pool is already connected [#2238](https://github.com/vatesfr/xen-orchestra/issues/2238) (PR [#3724](https://github.com/vatesfr/xen-orchestra/pull/3724))
- [VM] Pause (like Suspend but doesn't copy RAM on disk) [#3727](https://github.com/vatesfr/xen-orchestra/issues/3727) (PR [#3731](https://github.com/vatesfr/xen-orchestra/pull/3731))
### Bug fixes

View File

@@ -1080,6 +1080,20 @@ suspend.resolve = {
// -------------------------------------------------------------------
export async function pause({ vm }) {
await this.getXapi(vm).call('VM.pause', vm._xapiRef)
}
pause.params = {
id: { type: 'string' },
}
pause.resolve = {
vm: ['id', 'VM', 'operate'],
}
// -------------------------------------------------------------------
export function resume({ vm }) {
return this.getXapi(vm).resumeVm(vm._xapiId)
}

View File

@@ -1596,7 +1596,9 @@ export default class Xapi extends XapiBase {
throw forbiddenOperation('Start', e.params[1])
}
if (e.code === 'VM_BAD_POWER_STATE') {
return this.resumeVm(vmId)
return e.params[2] === 'paused'
? this.unpauseVm(vmId)
: this.resumeVm(vmId)
}
throw e
}

View File

@@ -449,7 +449,7 @@ export default {
}
await this.call('VM.revert', snapshot.$ref)
if (snapshot.snapshot_info['power-state-at-snapshot'] === 'Running') {
const vm = snapshot.$snapshot_of
const vm = await this.barrier(snapshot.snapshot_of)
if (vm.power_state === 'Halted') {
this.startVm(vm.$id)::ignoreErrors()
} else if (vm.power_state === 'Suspended') {
@@ -463,6 +463,10 @@ export default {
return this.call('VM.resume', this.getObject(vmId).$ref, false, true)
},
async unpauseVm(vmId) {
return this.call('VM.unpause', this.getObject(vmId).$ref)
},
shutdownVm(vmId, { hard = false } = {}) {
return this.call(
`VM.${hard ? 'hard' : 'clean'}_shutdown`,

View File

@@ -618,6 +618,7 @@ const messages = {
startVmOnMissingHostMessage: 'You must select a host',
recoveryModeLabel: 'Recovery start',
suspendVmLabel: 'Suspend',
pauseVmLabel: 'Pause',
stopVmLabel: 'Stop',
forceShutdownVmLabel: 'Force shutdown',
rebootVmLabel: 'Reboot',
@@ -871,6 +872,7 @@ const messages = {
powerStateHalted: 'halted',
powerStateRunning: 'running',
powerStateSuspended: 'suspended',
powerStatePaused: 'paused',
// ----- VM home -----
vmCurrentStatus: 'Current status:',
@@ -1459,6 +1461,9 @@ const messages = {
suspendVmsModalTitle: 'Suspend VM{vms, plural, one {} other {s}}',
suspendVmsModalMessage:
'Are you sure you want to suspend {vms, number} VM{vms, plural, one {} other {s}}?',
pauseVmsModalTitle: 'Pause VM{vms, plural, one {} other {s}}',
pauseVmsModalMessage:
'Are you sure you want to pause {vms, number} VM{vms, plural, one {} other {s}}?',
restartVmsModalTitle: 'Restart VM{vms, plural, one {} other {s}}',
restartVmsModalMessage:
'Are you sure you want to restart {vms, number} VM{vms, plural, one {} other {s}}?',
@@ -2075,7 +2080,7 @@ const messages = {
durationFormat:
'{days, plural, =0 {} one {# day } other {# days }}{hours, plural, =0 {} one {# hour } other {# hours }}{minutes, plural, =0 {} one {# minute } other {# minutes }}{seconds, plural, =0 {} one {# second} other {# seconds}}',
}
forEach(messages, function (message, id) {
forEach(messages, function(message, id) {
if (isString(message)) {
messages[id] = {
id,

View File

@@ -946,15 +946,21 @@ export const suspendVm = vm => _call('vm.suspend', { id: resolveId(vm) })
export const suspendVms = vms =>
confirm({
title: _('suspendVmsModalTitle', { nVms: vms.length }),
body: _('suspendVmsModalMessage', { nVms: vms.length }),
title: _('suspendVmsModalTitle', { vms: vms.length }),
body: _('suspendVmsModalMessage', { vms: vms.length }),
}).then(
() =>
Promise.all(map(vms, vm => _call('vm.suspend', { id: resolveId(vm) }))),
noop
)
export const resumeVm = vm => _call('vm.resume', { id: resolveId(vm) })
export const pauseVm = vm => _call('vm.pause', { id: resolveId(vm) })
export const pauseVms = vms =>
confirm({
title: _('pauseVmsModalTitle', { vms: vms.length }),
body: _('pauseVmsModalMessage', { vms: vms.length }),
}).then(() => Promise.all(map(vms, pauseVm)), noop)
export const recoveryStartVm = vm =>
_call('vm.recoveryStart', { id: resolveId(vm) })

View File

@@ -323,6 +323,11 @@
@extend .fa-desktop;
@extend .text-primary;
}
&-paused {
@extend .fa;
@extend .fa-desktop;
@extend .text-muted;
}
&-halted {
@extend .fa;
@extend .fa-desktop;
@@ -364,6 +369,10 @@
@extend .fa-copy;
}
&-suspend {
@extend .fa;
@extend .fa-power-off;
}
&-pause {
@extend .fa;
@extend .fa-pause;
}
@@ -377,7 +386,7 @@
}
&-force-shutdown {
@extend .fa;
@extend .fa-power-off;
@extend .fa-plug;
}
&-docker {
@extend .fa;
@@ -418,7 +427,7 @@
&-paused {
@extend .fa;
@extend .fa-circle;
@extend .xo-status-suspended;
@extend .xo-status-paused;
}
&-unknown {

View File

@@ -45,6 +45,7 @@ import {
forgetSrs,
isSrShared,
migrateVms,
pauseVms,
reconnectAllHostsSrs,
rescanSrs,
restartHosts,
@@ -149,6 +150,11 @@ const OPTIONS = {
{ handler: copyVms, icon: 'vm-copy', tooltip: _('copyVmLabel') },
],
otherActions: [
{
handler: pauseVms,
icon: 'vm-pause',
labelId: 'pauseVmLabel',
},
{
handler: suspendVms,
icon: 'vm-suspend',

View File

@@ -190,8 +190,8 @@ export default class Jobs extends Component {
'vm.delete',
'vm.migrate',
'vm.migrate',
'vm.pause',
'vm.restart',
'vm.resume',
'vm.revert',
'vm.rollingBackup',
'vm.rollingDrCopy',

View File

@@ -10,7 +10,6 @@ import {
exportVm,
migrateVm,
restartVm,
resumeVm,
snapshotVm,
startVm,
stopVm,
@@ -125,7 +124,7 @@ const vmActionBarByState = {
Suspended: ({ vm, isSelfUser, canAdministrate }) => (
<ActionBar display='icon' handlerParam={vm}>
<Action
handler={resumeVm}
handler={startVm}
icon='vm-start'
label={_('resumeVmLabel')}
pending={includes(vm.current_operations, 'start')}
@@ -156,6 +155,24 @@ const vmActionBarByState = {
)}
</ActionBar>
),
Paused: ({ vm, isSelfUser }) => (
<ActionBar display='icon' handlerParam={vm}>
<Action
handler={startVm}
icon='vm-start'
label={_('resumeVmLabel')}
pending={includes(vm.current_operations, 'unpause')}
/>
{!isSelfUser && (
<Action
handler={snapshotVm}
icon='vm-snapshot'
label={_('snapshotVmLabel')}
pending={includes(vm.current_operations, 'snapshot')}
/>
)}
</ActionBar>
),
}
const VmActionBar = addSubscriptions(() => ({
@@ -163,7 +180,10 @@ const VmActionBar = addSubscriptions(() => ({
}))(
connectStore(() => ({
checkPermissions: getCheckPermissions,
userId: createSelector(getUser, user => user.id),
userId: createSelector(
getUser,
user => user.id
),
}))(({ checkPermissions, vm, userId, resourceSets }) => {
// Is the user in the same resource set as the VM
const _getIsSelfUser = createSelector(

View File

@@ -32,9 +32,9 @@ import {
editVm,
getVmsHaValues,
isVmRunning,
pauseVm,
recoveryStartVm,
restartVm,
resumeVm,
shareVm,
startVm,
stopVm,
@@ -330,6 +330,13 @@ export default class TabAdvanced extends Component {
<Col className='text-xs-right'>
{vm.power_state === 'Running' && (
<span>
<TabButton
btnStyle='primary'
handler={pauseVm}
handlerParam={vm}
icon='vm-pause'
labelId='pauseVmLabel'
/>
<TabButton
btnStyle='primary'
handler={suspendVm}
@@ -381,7 +388,7 @@ export default class TabAdvanced extends Component {
<span>
<TabButton
btnStyle='primary'
handler={resumeVm}
handler={startVm}
handlerParam={vm}
icon='vm-start'
labelId='resumeVmLabel'
@@ -395,6 +402,31 @@ export default class TabAdvanced extends Component {
/>
</span>
)}
{vm.power_state === 'Paused' && (
<span>
<TabButton
btnStyle='primary'
handler={startVm}
handlerParam={vm}
icon='vm-start'
labelId='resumeVmLabel'
/>
<TabButton
btnStyle='warning'
handler={forceReboot}
handlerParam={vm}
icon='vm-force-reboot'
labelId='forceRebootVmLabel'
/>
<TabButton
btnStyle='warning'
handler={forceShutdown}
handlerParam={vm}
icon='vm-force-shutdown'
labelId='forceShutdownVmLabel'
/>
</span>
)}
<TabButton
btnStyle='danger'
disabled={vm.power_state !== 'Halted'}