feat(backup NG): new option to shutdown VMs before snapshot (#3060)

Fixes #3058
This commit is contained in:
badrAZ
2018-06-11 17:25:18 +02:00
committed by Julien Fontanet
parent 26c965faa9
commit bc72e67442
6 changed files with 59 additions and 13 deletions

View File

@@ -6,6 +6,7 @@
- [Delta Backup NG logs] Display wether the export is a full or a delta [#2711](https://github.com/vatesfr/xen-orchestra/issues/2711)
- Copy VDIs' UUID from SR/disks view [#3051](https://github.com/vatesfr/xen-orchestra/issues/3051)
- [Backup NG] New option to shutdown VMs before snapshotting them [#3058](https://github.com/vatesfr/xen-orchestra/issues/3058#event-1673756438)
### Bugs

View File

@@ -1017,13 +1017,12 @@ export async function stop ({ vm, force }) {
// Hard shutdown
if (force) {
await xapi.call('VM.hard_shutdown', vm._xapiRef)
return
return xapi.shutdownVm(vm._xapiRef, { hard: true })
}
// Clean shutdown
try {
await xapi.call('VM.clean_shutdown', vm._xapiRef)
await xapi.shutdownVm(vm._xapiRef)
} catch (error) {
const { code } = error
if (

View File

@@ -1,6 +1,6 @@
import deferrable from 'golike-defer'
import { catchPlus as pCatch, ignoreErrors } from 'promise-toolbox'
import { find, gte, includes, isEmpty, lte } from 'lodash'
import { find, gte, includes, isEmpty, lte, noop } from 'lodash'
import { forEach, mapToArray, parseSize } from '../../utils'
@@ -429,4 +429,11 @@ export default {
// the force parameter is always true
return this.call('VM.resume', this.getObject(vmId).$ref, false, true)
},
shutdownVm (vmId, { hard = false } = {}) {
return this.call(
`VM.${hard ? 'hard' : 'clean'}_shutdown`,
this.getObject(vmId).$ref
).then(noop)
},
}

View File

@@ -8,7 +8,11 @@ import { type Pattern, createPredicate } from 'value-matcher'
import { type Readable, PassThrough } from 'stream'
import { basename, dirname } from 'path'
import { isEmpty, last, mapValues, noop, some, sum, values } from 'lodash'
import { fromEvent as pFromEvent, timeout as pTimeout } from 'promise-toolbox'
import {
fromEvent as pFromEvent,
ignoreErrors,
timeout as pTimeout,
} from 'promise-toolbox'
import Vhd, {
chainVhd,
createSyntheticStream as createVhdReadStream,
@@ -40,6 +44,7 @@ type Settings = {|
concurrency?: number,
deleteFirst?: boolean,
exportRetention?: number,
offlineSnapshot?: boolean,
reportWhen?: ReportWhen,
snapshotRetention?: number,
vmTimeout?: number,
@@ -104,6 +109,7 @@ const getOldEntries = <T>(retention: number, entries?: T[]): T[] =>
const defaultSettings: Settings = {
deleteFirst: false,
exportRetention: 0,
offlineSnapshot: false,
reportWhen: 'failure',
snapshotRetention: 0,
vmTimeout: 0,
@@ -745,6 +751,17 @@ export default class BackupNg {
await xapi._assertHealthyVdiChains(vm)
const offlineSnapshot: boolean = getSetting(
settings,
'offlineSnapshot',
vmUuid,
''
)
const startAfterSnapshot = offlineSnapshot && vm.power_state === 'Running'
if (startAfterSnapshot) {
await xapi.shutdownVm(vm)
}
let snapshot: Vm = (await wrapTask(
{
parentId: taskId,
@@ -758,6 +775,11 @@ export default class BackupNg {
`[XO Backup ${job.name}] ${vm.name_label}`
)
): any)
if (startAfterSnapshot) {
ignoreErrors.call(xapi.startVm(vm))
}
await xapi._updateObjectMapProperty(snapshot, 'other_config', {
'xo:backup:job': jobId,
'xo:backup:schedule': scheduleId,

View File

@@ -364,6 +364,7 @@ const messages = {
backupName: 'Name',
useDelta: 'Use delta',
useCompression: 'Use compression',
offlineSnapshot: 'Offline snapshot',
dbAndDrRequireEntreprisePlan: 'Delta Backup and DR require Entreprise plan',
crRequiresPremiumPlan: 'CR requires Premium plan',
smartBackupModeTitle: 'Smart mode',

View File

@@ -13,7 +13,6 @@ import {
find,
findKey,
flatten,
get,
includes,
isEmpty,
keyBy,
@@ -120,6 +119,7 @@ const getInitialState = () => ({
formId: getRandomId(),
name: '',
newSchedules: {},
offlineSnapshot: false,
paramsUpdated: false,
powerState: 'All',
remotes: [],
@@ -160,6 +160,7 @@ export default [
'': {
reportWhen: state.reportWhen,
concurrency: state.concurrency || undefined,
offlineSnapshot: state.offlineSnapshot,
},
},
remotes:
@@ -230,6 +231,7 @@ export default [
if (id === '') {
oldSetting.reportWhen = state.reportWhen
oldSetting.concurrency = state.concurrency || undefined
oldSetting.offlineSnapshot = state.offlineSnapshot
} else if (!(id in settings)) {
delete oldSettings[id]
} else if (
@@ -269,9 +271,9 @@ export default [
...state,
[mode]: !state[mode],
}),
setCompression: (_, { target: { checked } }) => state => ({
setCheckboxValue: (_, { target: { checked, name } }) => state => ({
...state,
compression: checked,
[name]: checked,
}),
toggleSmartMode: (_, smartMode) => state => ({
...state,
@@ -312,7 +314,8 @@ export default [
const remotes =
job.remotes !== undefined ? destructPattern(job.remotes) : []
const srs = job.srs !== undefined ? destructPattern(job.srs) : []
const globalSettings = job.settings['']
const { concurrency, reportWhen, offlineSnapshot } =
job.settings[''] || {}
const settings = { ...job.settings }
delete settings['']
@@ -332,8 +335,9 @@ export default [
crMode: job.mode === 'delta' && !isEmpty(srs),
remotes,
srs,
reportWhen: get(globalSettings, 'reportWhen') || 'failure',
concurrency: get(globalSettings, 'concurrency') || 0,
reportWhen: reportWhen || 'failure',
concurrency: concurrency || 0,
offlineSnapshot,
settings,
schedules,
...destructVmsPattern(job.vms),
@@ -586,9 +590,10 @@ export default [
{state.showCompression && (
<label>
<input
type='checkbox'
onChange={effects.setCompression}
checked={state.compression}
name='compression'
onChange={effects.setCheckboxValue}
type='checkbox'
/>{' '}
<strong>{_('useCompression')}</strong>
</label>
@@ -768,6 +773,17 @@ export default [
value={state.concurrency}
/>
</FormGroup>
<FormGroup>
<label>
<strong>{_('offlineSnapshot')}</strong>{' '}
<input
checked={state.offlineSnapshot}
name='offlineSnapshot'
onChange={effects.setCheckboxValue}
type='checkbox'
/>
</label>
</FormGroup>
</CardBlock>
</Card>
</Col>