@xen-orchestra/backups 0.36.1 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Backup.js +11 -302
- package/ImportVmBackup.js +6 -6
- package/RemoteAdapter.js +20 -13
- package/RestoreMetadataBackup.js +1 -1
- package/_backupWorker.js +2 -2
- package/{_deltaVm.js → _incrementalVm.js} +11 -11
- package/_runners/Metadata.js +134 -0
- package/_runners/VmsXapi.js +138 -0
- package/_runners/_Abstract.js +51 -0
- package/{_PoolMetadataBackup.js → _runners/_PoolMetadataBackup.js} +3 -3
- package/_runners/_RemoteTimeoutError.js +8 -0
- package/{_XoMetadataBackup.js → _runners/_XoMetadataBackup.js} +3 -3
- package/_runners/_getAdaptersByRemote.js +9 -0
- package/_runners/_runTask.js +6 -0
- package/_runners/_vmRunners/FullXapi.js +61 -0
- package/_runners/_vmRunners/IncrementalXapi.js +163 -0
- package/_runners/_vmRunners/_Abstract.js +87 -0
- package/_runners/_vmRunners/_AbstractXapi.js +258 -0
- package/_runners/_vmRunners/_forkDeltaExport.js +12 -0
- package/{writers/FullBackupWriter.js → _runners/_writers/FullRemoteWriter.js} +5 -5
- package/{writers/FullReplicationWriter.js → _runners/_writers/FullXapiWriter.js} +5 -5
- package/{writers/DeltaBackupWriter.js → _runners/_writers/IncrementalRemoteWriter.js} +7 -7
- package/{writers/DeltaReplicationWriter.js → _runners/_writers/IncrementalXapiWriter.js} +8 -8
- package/{writers/_AbstractDeltaWriter.js → _runners/_writers/_AbstractIncrementalWriter.js} +1 -1
- package/{writers/_MixinBackupWriter.js → _runners/_writers/_MixinRemoteWriter.js} +10 -10
- package/{writers/_MixinReplicationWriter.js → _runners/_writers/_MixinXapiWriter.js} +6 -12
- package/package.json +5 -5
- package/_VmBackup.js +0 -515
- /package/{_createStreamThrottle.js → _runners/_createStreamThrottle.js} +0 -0
- /package/{_forkStreamUnpipe.js → _runners/_forkStreamUnpipe.js} +0 -0
- /package/{writers → _runners/_writers}/_AbstractFullWriter.js +0 -0
- /package/{writers → _runners/_writers}/_AbstractWriter.js +0 -0
- /package/{writers → _runners/_writers}/_checkVhd.js +0 -0
- /package/{writers → _runners/_writers}/_listReplicatedVms.js +0 -0
- /package/{writers → _runners/_writers}/_packUuid.js +0 -0
package/_VmBackup.js
DELETED
|
@@ -1,515 +0,0 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const assert = require('assert')
|
|
4
|
-
const findLast = require('lodash/findLast.js')
|
|
5
|
-
const groupBy = require('lodash/groupBy.js')
|
|
6
|
-
const ignoreErrors = require('promise-toolbox/ignoreErrors')
|
|
7
|
-
const keyBy = require('lodash/keyBy.js')
|
|
8
|
-
const mapValues = require('lodash/mapValues.js')
|
|
9
|
-
const vhdStreamValidator = require('vhd-lib/vhdStreamValidator.js')
|
|
10
|
-
const { asyncMap } = require('@xen-orchestra/async-map')
|
|
11
|
-
const { createLogger } = require('@xen-orchestra/log')
|
|
12
|
-
const { decorateMethodsWith } = require('@vates/decorate-with')
|
|
13
|
-
const { defer } = require('golike-defer')
|
|
14
|
-
const { formatDateTime } = require('@xen-orchestra/xapi')
|
|
15
|
-
const { pipeline } = require('node:stream')
|
|
16
|
-
|
|
17
|
-
const { DeltaBackupWriter } = require('./writers/DeltaBackupWriter.js')
|
|
18
|
-
const { DeltaReplicationWriter } = require('./writers/DeltaReplicationWriter.js')
|
|
19
|
-
const { exportDeltaVm } = require('./_deltaVm.js')
|
|
20
|
-
const { forkStreamUnpipe } = require('./_forkStreamUnpipe.js')
|
|
21
|
-
const { FullBackupWriter } = require('./writers/FullBackupWriter.js')
|
|
22
|
-
const { FullReplicationWriter } = require('./writers/FullReplicationWriter.js')
|
|
23
|
-
const { getOldEntries } = require('./_getOldEntries.js')
|
|
24
|
-
const { Task } = require('./Task.js')
|
|
25
|
-
const { watchStreamSize } = require('./_watchStreamSize.js')
|
|
26
|
-
|
|
27
|
-
const { debug, warn } = createLogger('xo:backups:VmBackup')
|
|
28
|
-
|
|
29
|
-
class AggregateError extends Error {
|
|
30
|
-
constructor(errors, message) {
|
|
31
|
-
super(message)
|
|
32
|
-
this.errors = errors
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const asyncEach = async (iterable, fn, thisArg = iterable) => {
|
|
37
|
-
for (const item of iterable) {
|
|
38
|
-
await fn.call(thisArg, item)
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const forkDeltaExport = deltaExport =>
|
|
43
|
-
Object.create(deltaExport, {
|
|
44
|
-
streams: {
|
|
45
|
-
value: mapValues(deltaExport.streams, forkStreamUnpipe),
|
|
46
|
-
},
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
const noop = Function.prototype
|
|
50
|
-
|
|
51
|
-
class VmBackup {
|
|
52
|
-
constructor({
|
|
53
|
-
config,
|
|
54
|
-
getSnapshotNameLabel,
|
|
55
|
-
healthCheckSr,
|
|
56
|
-
job,
|
|
57
|
-
remoteAdapters,
|
|
58
|
-
remotes,
|
|
59
|
-
schedule,
|
|
60
|
-
settings,
|
|
61
|
-
srs,
|
|
62
|
-
throttleStream,
|
|
63
|
-
vm,
|
|
64
|
-
}) {
|
|
65
|
-
if (vm.other_config['xo:backup:job'] === job.id && 'start' in vm.blocked_operations) {
|
|
66
|
-
// don't match replicated VMs created by this very job otherwise they
|
|
67
|
-
// will be replicated again and again
|
|
68
|
-
throw new Error('cannot backup a VM created by this very job')
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
this.config = config
|
|
72
|
-
this.job = job
|
|
73
|
-
this.remoteAdapters = remoteAdapters
|
|
74
|
-
this.scheduleId = schedule.id
|
|
75
|
-
this.timestamp = undefined
|
|
76
|
-
|
|
77
|
-
// VM currently backed up
|
|
78
|
-
this.vm = vm
|
|
79
|
-
const { tags } = this.vm
|
|
80
|
-
|
|
81
|
-
// VM (snapshot) that is really exported
|
|
82
|
-
this.exportedVm = undefined
|
|
83
|
-
|
|
84
|
-
this._fullVdisRequired = undefined
|
|
85
|
-
this._getSnapshotNameLabel = getSnapshotNameLabel
|
|
86
|
-
this._isDelta = job.mode === 'delta'
|
|
87
|
-
this._healthCheckSr = healthCheckSr
|
|
88
|
-
this._jobId = job.id
|
|
89
|
-
this._jobSnapshots = undefined
|
|
90
|
-
this._throttleStream = throttleStream
|
|
91
|
-
this._xapi = vm.$xapi
|
|
92
|
-
|
|
93
|
-
// Base VM for the export
|
|
94
|
-
this._baseVm = undefined
|
|
95
|
-
|
|
96
|
-
// Settings for this specific run (job, schedule, VM)
|
|
97
|
-
if (tags.includes('xo-memory-backup')) {
|
|
98
|
-
settings.checkpointSnapshot = true
|
|
99
|
-
}
|
|
100
|
-
if (tags.includes('xo-offline-backup')) {
|
|
101
|
-
settings.offlineSnapshot = true
|
|
102
|
-
}
|
|
103
|
-
this._settings = settings
|
|
104
|
-
|
|
105
|
-
// Create writers
|
|
106
|
-
{
|
|
107
|
-
const writers = new Set()
|
|
108
|
-
this._writers = writers
|
|
109
|
-
|
|
110
|
-
const [BackupWriter, ReplicationWriter] = this._isDelta
|
|
111
|
-
? [DeltaBackupWriter, DeltaReplicationWriter]
|
|
112
|
-
: [FullBackupWriter, FullReplicationWriter]
|
|
113
|
-
|
|
114
|
-
const allSettings = job.settings
|
|
115
|
-
Object.keys(remoteAdapters).forEach(remoteId => {
|
|
116
|
-
const targetSettings = {
|
|
117
|
-
...settings,
|
|
118
|
-
...allSettings[remoteId],
|
|
119
|
-
}
|
|
120
|
-
if (targetSettings.exportRetention !== 0) {
|
|
121
|
-
writers.add(new BackupWriter({ backup: this, remoteId, settings: targetSettings }))
|
|
122
|
-
}
|
|
123
|
-
})
|
|
124
|
-
srs.forEach(sr => {
|
|
125
|
-
const targetSettings = {
|
|
126
|
-
...settings,
|
|
127
|
-
...allSettings[sr.uuid],
|
|
128
|
-
}
|
|
129
|
-
if (targetSettings.copyRetention !== 0) {
|
|
130
|
-
writers.add(new ReplicationWriter({ backup: this, sr, settings: targetSettings }))
|
|
131
|
-
}
|
|
132
|
-
})
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
// calls fn for each function, warns of any errors, and throws only if there are no writers left
|
|
137
|
-
async _callWriters(fn, step, parallel = true) {
|
|
138
|
-
const writers = this._writers
|
|
139
|
-
const n = writers.size
|
|
140
|
-
if (n === 0) {
|
|
141
|
-
return
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
async function callWriter(writer) {
|
|
145
|
-
const { name } = writer.constructor
|
|
146
|
-
try {
|
|
147
|
-
debug('writer step starting', { step, writer: name })
|
|
148
|
-
await fn(writer)
|
|
149
|
-
debug('writer step succeeded', { duration: step, writer: name })
|
|
150
|
-
} catch (error) {
|
|
151
|
-
writers.delete(writer)
|
|
152
|
-
|
|
153
|
-
warn('writer step failed', { error, step, writer: name })
|
|
154
|
-
|
|
155
|
-
// these two steps are the only one that are not already in their own sub tasks
|
|
156
|
-
if (step === 'writer.checkBaseVdis()' || step === 'writer.beforeBackup()') {
|
|
157
|
-
Task.warning(
|
|
158
|
-
`the writer ${name} has failed the step ${step} with error ${error.message}. It won't be used anymore in this job execution.`
|
|
159
|
-
)
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
throw error
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
if (n === 1) {
|
|
166
|
-
const [writer] = writers
|
|
167
|
-
return callWriter(writer)
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
const errors = []
|
|
171
|
-
await (parallel ? asyncMap : asyncEach)(writers, async function (writer) {
|
|
172
|
-
try {
|
|
173
|
-
await callWriter(writer)
|
|
174
|
-
} catch (error) {
|
|
175
|
-
errors.push(error)
|
|
176
|
-
}
|
|
177
|
-
})
|
|
178
|
-
if (writers.size === 0) {
|
|
179
|
-
throw new AggregateError(errors, 'all targets have failed, step: ' + step)
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// ensure the VM itself does not have any backup metadata which would be
|
|
184
|
-
// copied on manual snapshots and interfere with the backup jobs
|
|
185
|
-
async _cleanMetadata() {
|
|
186
|
-
const { vm } = this
|
|
187
|
-
if ('xo:backup:job' in vm.other_config) {
|
|
188
|
-
await vm.update_other_config({
|
|
189
|
-
'xo:backup:datetime': null,
|
|
190
|
-
'xo:backup:deltaChainLength': null,
|
|
191
|
-
'xo:backup:exported': null,
|
|
192
|
-
'xo:backup:job': null,
|
|
193
|
-
'xo:backup:schedule': null,
|
|
194
|
-
'xo:backup:vm': null,
|
|
195
|
-
})
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
async _snapshot() {
|
|
200
|
-
const { vm } = this
|
|
201
|
-
const xapi = this._xapi
|
|
202
|
-
|
|
203
|
-
const settings = this._settings
|
|
204
|
-
|
|
205
|
-
const doSnapshot =
|
|
206
|
-
settings.unconditionalSnapshot ||
|
|
207
|
-
this._isDelta ||
|
|
208
|
-
(!settings.offlineBackup && vm.power_state === 'Running') ||
|
|
209
|
-
settings.snapshotRetention !== 0
|
|
210
|
-
if (doSnapshot) {
|
|
211
|
-
await Task.run({ name: 'snapshot' }, async () => {
|
|
212
|
-
if (!settings.bypassVdiChainsCheck) {
|
|
213
|
-
await vm.$assertHealthyVdiChains()
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const snapshotRef = await vm[settings.checkpointSnapshot ? '$checkpoint' : '$snapshot']({
|
|
217
|
-
ignoreNobakVdis: true,
|
|
218
|
-
name_label: this._getSnapshotNameLabel(vm),
|
|
219
|
-
unplugVusbs: true,
|
|
220
|
-
})
|
|
221
|
-
this.timestamp = Date.now()
|
|
222
|
-
|
|
223
|
-
await xapi.setFieldEntries('VM', snapshotRef, 'other_config', {
|
|
224
|
-
'xo:backup:datetime': formatDateTime(this.timestamp),
|
|
225
|
-
'xo:backup:job': this._jobId,
|
|
226
|
-
'xo:backup:schedule': this.scheduleId,
|
|
227
|
-
'xo:backup:vm': vm.uuid,
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
this.exportedVm = await xapi.getRecord('VM', snapshotRef)
|
|
231
|
-
|
|
232
|
-
return this.exportedVm.uuid
|
|
233
|
-
})
|
|
234
|
-
} else {
|
|
235
|
-
this.exportedVm = vm
|
|
236
|
-
this.timestamp = Date.now()
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
async _copyDelta() {
|
|
241
|
-
const { exportedVm } = this
|
|
242
|
-
const baseVm = this._baseVm
|
|
243
|
-
const fullVdisRequired = this._fullVdisRequired
|
|
244
|
-
|
|
245
|
-
const isFull = fullVdisRequired === undefined || fullVdisRequired.size !== 0
|
|
246
|
-
|
|
247
|
-
await this._callWriters(writer => writer.prepare({ isFull }), 'writer.prepare()')
|
|
248
|
-
|
|
249
|
-
const deltaExport = await exportDeltaVm(exportedVm, baseVm, {
|
|
250
|
-
fullVdisRequired,
|
|
251
|
-
})
|
|
252
|
-
// since NBD is network based, if one disk use nbd , all the disk use them
|
|
253
|
-
// except the suspended VDI
|
|
254
|
-
if (Object.values(deltaExport.streams).some(({ _nbd }) => _nbd)) {
|
|
255
|
-
Task.info('Transfer data using NBD')
|
|
256
|
-
}
|
|
257
|
-
const sizeContainers = mapValues(deltaExport.streams, stream => watchStreamSize(stream))
|
|
258
|
-
|
|
259
|
-
if (this._settings.validateVhdStreams) {
|
|
260
|
-
deltaExport.streams = mapValues(deltaExport.streams, stream => pipeline(stream, vhdStreamValidator, noop))
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
deltaExport.streams = mapValues(deltaExport.streams, this._throttleStream)
|
|
264
|
-
|
|
265
|
-
const timestamp = Date.now()
|
|
266
|
-
|
|
267
|
-
await this._callWriters(
|
|
268
|
-
writer =>
|
|
269
|
-
writer.transfer({
|
|
270
|
-
deltaExport: forkDeltaExport(deltaExport),
|
|
271
|
-
sizeContainers,
|
|
272
|
-
timestamp,
|
|
273
|
-
}),
|
|
274
|
-
'writer.transfer()'
|
|
275
|
-
)
|
|
276
|
-
|
|
277
|
-
this._baseVm = exportedVm
|
|
278
|
-
|
|
279
|
-
if (baseVm !== undefined) {
|
|
280
|
-
await exportedVm.update_other_config(
|
|
281
|
-
'xo:backup:deltaChainLength',
|
|
282
|
-
String(+(baseVm.other_config['xo:backup:deltaChainLength'] ?? 0) + 1)
|
|
283
|
-
)
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
// not the case if offlineBackup
|
|
287
|
-
if (exportedVm.is_a_snapshot) {
|
|
288
|
-
await exportedVm.update_other_config('xo:backup:exported', 'true')
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
const size = Object.values(sizeContainers).reduce((sum, { size }) => sum + size, 0)
|
|
292
|
-
const end = Date.now()
|
|
293
|
-
const duration = end - timestamp
|
|
294
|
-
debug('transfer complete', {
|
|
295
|
-
duration,
|
|
296
|
-
speed: duration !== 0 ? (size * 1e3) / 1024 / 1024 / duration : 0,
|
|
297
|
-
size,
|
|
298
|
-
})
|
|
299
|
-
|
|
300
|
-
await this._callWriters(writer => writer.cleanup(), 'writer.cleanup()')
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
async _copyFull() {
|
|
304
|
-
const { compression } = this.job
|
|
305
|
-
const stream = this._throttleStream(
|
|
306
|
-
await this._xapi.VM_export(this.exportedVm.$ref, {
|
|
307
|
-
compress: Boolean(compression) && (compression === 'native' ? 'gzip' : 'zstd'),
|
|
308
|
-
useSnapshot: false,
|
|
309
|
-
})
|
|
310
|
-
)
|
|
311
|
-
const sizeContainer = watchStreamSize(stream)
|
|
312
|
-
|
|
313
|
-
const timestamp = Date.now()
|
|
314
|
-
|
|
315
|
-
await this._callWriters(
|
|
316
|
-
writer =>
|
|
317
|
-
writer.run({
|
|
318
|
-
sizeContainer,
|
|
319
|
-
stream: forkStreamUnpipe(stream),
|
|
320
|
-
timestamp,
|
|
321
|
-
}),
|
|
322
|
-
'writer.run()'
|
|
323
|
-
)
|
|
324
|
-
|
|
325
|
-
const { size } = sizeContainer
|
|
326
|
-
const end = Date.now()
|
|
327
|
-
const duration = end - timestamp
|
|
328
|
-
debug('transfer complete', {
|
|
329
|
-
duration,
|
|
330
|
-
speed: duration !== 0 ? (size * 1e3) / 1024 / 1024 / duration : 0,
|
|
331
|
-
size,
|
|
332
|
-
})
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
async _fetchJobSnapshots() {
|
|
336
|
-
const jobId = this._jobId
|
|
337
|
-
const vmRef = this.vm.$ref
|
|
338
|
-
const xapi = this._xapi
|
|
339
|
-
|
|
340
|
-
const snapshotsRef = await xapi.getField('VM', vmRef, 'snapshots')
|
|
341
|
-
const snapshotsOtherConfig = await asyncMap(snapshotsRef, ref => xapi.getField('VM', ref, 'other_config'))
|
|
342
|
-
|
|
343
|
-
const snapshots = []
|
|
344
|
-
snapshotsOtherConfig.forEach((other_config, i) => {
|
|
345
|
-
if (other_config['xo:backup:job'] === jobId) {
|
|
346
|
-
snapshots.push({ other_config, $ref: snapshotsRef[i] })
|
|
347
|
-
}
|
|
348
|
-
})
|
|
349
|
-
snapshots.sort((a, b) => (a.other_config['xo:backup:datetime'] < b.other_config['xo:backup:datetime'] ? -1 : 1))
|
|
350
|
-
this._jobSnapshots = snapshots
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
async _removeUnusedSnapshots() {
|
|
354
|
-
const allSettings = this.job.settings
|
|
355
|
-
const baseSettings = this._baseSettings
|
|
356
|
-
const baseVmRef = this._baseVm?.$ref
|
|
357
|
-
|
|
358
|
-
const snapshotsPerSchedule = groupBy(this._jobSnapshots, _ => _.other_config['xo:backup:schedule'])
|
|
359
|
-
const xapi = this._xapi
|
|
360
|
-
await asyncMap(Object.entries(snapshotsPerSchedule), ([scheduleId, snapshots]) => {
|
|
361
|
-
const settings = {
|
|
362
|
-
...baseSettings,
|
|
363
|
-
...allSettings[scheduleId],
|
|
364
|
-
...allSettings[this.vm.uuid],
|
|
365
|
-
}
|
|
366
|
-
return asyncMap(getOldEntries(settings.snapshotRetention, snapshots), ({ $ref }) => {
|
|
367
|
-
if ($ref !== baseVmRef) {
|
|
368
|
-
return xapi.VM_destroy($ref)
|
|
369
|
-
}
|
|
370
|
-
})
|
|
371
|
-
})
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
async _selectBaseVm() {
|
|
375
|
-
const xapi = this._xapi
|
|
376
|
-
|
|
377
|
-
let baseVm = findLast(this._jobSnapshots, _ => 'xo:backup:exported' in _.other_config)
|
|
378
|
-
if (baseVm === undefined) {
|
|
379
|
-
debug('no base VM found')
|
|
380
|
-
return
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
const fullInterval = this._settings.fullInterval
|
|
384
|
-
const deltaChainLength = +(baseVm.other_config['xo:backup:deltaChainLength'] ?? 0) + 1
|
|
385
|
-
if (!(fullInterval === 0 || fullInterval > deltaChainLength)) {
|
|
386
|
-
debug('not using base VM becaust fullInterval reached')
|
|
387
|
-
return
|
|
388
|
-
}
|
|
389
|
-
|
|
390
|
-
const srcVdis = keyBy(await xapi.getRecords('VDI', await this.vm.$getDisks()), '$ref')
|
|
391
|
-
|
|
392
|
-
// resolve full record
|
|
393
|
-
baseVm = await xapi.getRecord('VM', baseVm.$ref)
|
|
394
|
-
|
|
395
|
-
const baseUuidToSrcVdi = new Map()
|
|
396
|
-
await asyncMap(await baseVm.$getDisks(), async baseRef => {
|
|
397
|
-
const [baseUuid, snapshotOf] = await Promise.all([
|
|
398
|
-
xapi.getField('VDI', baseRef, 'uuid'),
|
|
399
|
-
xapi.getField('VDI', baseRef, 'snapshot_of'),
|
|
400
|
-
])
|
|
401
|
-
const srcVdi = srcVdis[snapshotOf]
|
|
402
|
-
if (srcVdi !== undefined) {
|
|
403
|
-
baseUuidToSrcVdi.set(baseUuid, srcVdi)
|
|
404
|
-
} else {
|
|
405
|
-
debug('ignore snapshot VDI because no longer present on VM', {
|
|
406
|
-
vdi: baseUuid,
|
|
407
|
-
})
|
|
408
|
-
}
|
|
409
|
-
})
|
|
410
|
-
|
|
411
|
-
const presentBaseVdis = new Map(baseUuidToSrcVdi)
|
|
412
|
-
await this._callWriters(
|
|
413
|
-
writer => presentBaseVdis.size !== 0 && writer.checkBaseVdis(presentBaseVdis, baseVm),
|
|
414
|
-
'writer.checkBaseVdis()',
|
|
415
|
-
false
|
|
416
|
-
)
|
|
417
|
-
|
|
418
|
-
if (presentBaseVdis.size === 0) {
|
|
419
|
-
debug('no base VM found')
|
|
420
|
-
return
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
const fullVdisRequired = new Set()
|
|
424
|
-
baseUuidToSrcVdi.forEach((srcVdi, baseUuid) => {
|
|
425
|
-
if (presentBaseVdis.has(baseUuid)) {
|
|
426
|
-
debug('found base VDI', {
|
|
427
|
-
base: baseUuid,
|
|
428
|
-
vdi: srcVdi.uuid,
|
|
429
|
-
})
|
|
430
|
-
} else {
|
|
431
|
-
debug('missing base VDI', {
|
|
432
|
-
base: baseUuid,
|
|
433
|
-
vdi: srcVdi.uuid,
|
|
434
|
-
})
|
|
435
|
-
fullVdisRequired.add(srcVdi.uuid)
|
|
436
|
-
}
|
|
437
|
-
})
|
|
438
|
-
|
|
439
|
-
this._baseVm = baseVm
|
|
440
|
-
this._fullVdisRequired = fullVdisRequired
|
|
441
|
-
}
|
|
442
|
-
|
|
443
|
-
async _healthCheck() {
|
|
444
|
-
const settings = this._settings
|
|
445
|
-
|
|
446
|
-
if (this._healthCheckSr === undefined) {
|
|
447
|
-
return
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// check if current VM has tags
|
|
451
|
-
const { tags } = this.vm
|
|
452
|
-
const intersect = settings.healthCheckVmsWithTags.some(t => tags.includes(t))
|
|
453
|
-
|
|
454
|
-
if (settings.healthCheckVmsWithTags.length !== 0 && !intersect) {
|
|
455
|
-
return
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
await this._callWriters(writer => writer.healthCheck(this._healthCheckSr), 'writer.healthCheck()')
|
|
459
|
-
}
|
|
460
|
-
|
|
461
|
-
async run($defer) {
|
|
462
|
-
const settings = this._settings
|
|
463
|
-
assert(
|
|
464
|
-
!settings.offlineBackup || settings.snapshotRetention === 0,
|
|
465
|
-
'offlineBackup is not compatible with snapshotRetention'
|
|
466
|
-
)
|
|
467
|
-
|
|
468
|
-
await this._callWriters(async writer => {
|
|
469
|
-
await writer.beforeBackup()
|
|
470
|
-
$defer(async () => {
|
|
471
|
-
await writer.afterBackup()
|
|
472
|
-
})
|
|
473
|
-
}, 'writer.beforeBackup()')
|
|
474
|
-
|
|
475
|
-
await this._fetchJobSnapshots()
|
|
476
|
-
|
|
477
|
-
if (this._isDelta) {
|
|
478
|
-
await this._selectBaseVm()
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
await this._cleanMetadata()
|
|
482
|
-
await this._removeUnusedSnapshots()
|
|
483
|
-
|
|
484
|
-
const { vm } = this
|
|
485
|
-
const isRunning = vm.power_state === 'Running'
|
|
486
|
-
const startAfter = isRunning && (settings.offlineBackup ? 'backup' : settings.offlineSnapshot && 'snapshot')
|
|
487
|
-
if (startAfter) {
|
|
488
|
-
await vm.$callAsync('clean_shutdown')
|
|
489
|
-
}
|
|
490
|
-
|
|
491
|
-
try {
|
|
492
|
-
await this._snapshot()
|
|
493
|
-
if (startAfter === 'snapshot') {
|
|
494
|
-
ignoreErrors.call(vm.$callAsync('start', false, false))
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
if (this._writers.size !== 0) {
|
|
498
|
-
await (this._isDelta ? this._copyDelta() : this._copyFull())
|
|
499
|
-
}
|
|
500
|
-
} finally {
|
|
501
|
-
if (startAfter) {
|
|
502
|
-
ignoreErrors.call(vm.$callAsync('start', false, false))
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
await this._fetchJobSnapshots()
|
|
506
|
-
await this._removeUnusedSnapshots()
|
|
507
|
-
}
|
|
508
|
-
await this._healthCheck()
|
|
509
|
-
}
|
|
510
|
-
}
|
|
511
|
-
exports.VmBackup = VmBackup
|
|
512
|
-
|
|
513
|
-
decorateMethodsWith(VmBackup, {
|
|
514
|
-
run: defer,
|
|
515
|
-
})
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|