@xen-orchestra/backups 0.14.0 → 0.16.1
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/RemoteAdapter.js +81 -48
- package/_VmBackup.js +28 -13
- package/_cleanVm.js +48 -34
- package/package.json +7 -6
- package/writers/DeltaBackupWriter.js +14 -13
- package/writers/_checkVhd.js +3 -2
package/RemoteAdapter.js
CHANGED
|
@@ -3,19 +3,19 @@ const Disposable = require('promise-toolbox/Disposable.js')
|
|
|
3
3
|
const fromCallback = require('promise-toolbox/fromCallback.js')
|
|
4
4
|
const fromEvent = require('promise-toolbox/fromEvent.js')
|
|
5
5
|
const pDefer = require('promise-toolbox/defer.js')
|
|
6
|
-
const
|
|
7
|
-
const { basename, dirname, join, normalize, resolve } = require('path')
|
|
6
|
+
const { dirname, join, normalize, resolve } = require('path')
|
|
8
7
|
const { createLogger } = require('@xen-orchestra/log')
|
|
9
|
-
const {
|
|
8
|
+
const { Constants, createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdSynthetic } = require('vhd-lib')
|
|
10
9
|
const { deduped } = require('@vates/disposable/deduped.js')
|
|
11
10
|
const { execFile } = require('child_process')
|
|
12
11
|
const { readdir, stat } = require('fs-extra')
|
|
12
|
+
const { v4: uuidv4 } = require('uuid')
|
|
13
13
|
const { ZipFile } = require('yazl')
|
|
14
14
|
|
|
15
15
|
const { BACKUP_DIR } = require('./_getVmBackupDir.js')
|
|
16
16
|
const { cleanVm } = require('./_cleanVm.js')
|
|
17
17
|
const { getTmpDir } = require('./_getTmpDir.js')
|
|
18
|
-
const { isMetadataFile
|
|
18
|
+
const { isMetadataFile } = require('./_backupType.js')
|
|
19
19
|
const { isValidXva } = require('./_isValidXva.js')
|
|
20
20
|
const { listPartitions, LVM_PARTITION_TYPE } = require('./_listPartitions.js')
|
|
21
21
|
const { lvs, pvs } = require('./_lvm.js')
|
|
@@ -77,48 +77,6 @@ class RemoteAdapter {
|
|
|
77
77
|
return this._handler
|
|
78
78
|
}
|
|
79
79
|
|
|
80
|
-
async _deleteVhd(path) {
|
|
81
|
-
const handler = this._handler
|
|
82
|
-
const vhds = await asyncMapSettled(
|
|
83
|
-
await handler.list(dirname(path), {
|
|
84
|
-
filter: isVhdFile,
|
|
85
|
-
prependDir: true,
|
|
86
|
-
}),
|
|
87
|
-
async path => {
|
|
88
|
-
try {
|
|
89
|
-
const vhd = new Vhd(handler, path)
|
|
90
|
-
await vhd.readHeaderAndFooter()
|
|
91
|
-
return {
|
|
92
|
-
footer: vhd.footer,
|
|
93
|
-
header: vhd.header,
|
|
94
|
-
path,
|
|
95
|
-
}
|
|
96
|
-
} catch (error) {
|
|
97
|
-
// Do not fail on corrupted VHDs (usually uncleaned temporary files),
|
|
98
|
-
// they are probably inconsequent to the backup process and should not
|
|
99
|
-
// fail it.
|
|
100
|
-
warn(`BackupNg#_deleteVhd ${path}`, { error })
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
)
|
|
104
|
-
const base = basename(path)
|
|
105
|
-
const child = vhds.find(_ => _ !== undefined && _.header.parentUnicodeName === base)
|
|
106
|
-
if (child === undefined) {
|
|
107
|
-
await handler.unlink(path)
|
|
108
|
-
return 0
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
const childPath = child.path
|
|
113
|
-
const mergedDataSize = await mergeVhd(handler, path, handler, childPath)
|
|
114
|
-
await handler.rename(path, childPath)
|
|
115
|
-
return mergedDataSize
|
|
116
|
-
} catch (error) {
|
|
117
|
-
handler.unlink(path).catch(warn)
|
|
118
|
-
throw error
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
80
|
async _findPartition(devicePath, partitionId) {
|
|
123
81
|
const partitions = await listPartitions(devicePath)
|
|
124
82
|
const partition = partitions.find(_ => _.id === partitionId)
|
|
@@ -255,7 +213,7 @@ class RemoteAdapter {
|
|
|
255
213
|
const handler = this._handler
|
|
256
214
|
|
|
257
215
|
// unused VHDs will be detected by `cleanVm`
|
|
258
|
-
await asyncMapSettled(backups, ({ _filename }) =>
|
|
216
|
+
await asyncMapSettled(backups, ({ _filename }) => VhdAbstract.unlink(handler, _filename))
|
|
259
217
|
}
|
|
260
218
|
|
|
261
219
|
async deleteMetadataBackup(backupId) {
|
|
@@ -354,6 +312,17 @@ class RemoteAdapter {
|
|
|
354
312
|
return yield this._getPartition(devicePath, await this._findPartition(devicePath, partitionId))
|
|
355
313
|
}
|
|
356
314
|
|
|
315
|
+
// this function will be the one where we plug the logic of the storage format by fs type/user settings
|
|
316
|
+
|
|
317
|
+
// if the file is named .vhd => vhd
|
|
318
|
+
// if the file is named alias.vhd => alias to a vhd
|
|
319
|
+
getVhdFileName(baseName) {
|
|
320
|
+
if (this._handler.type === 's3') {
|
|
321
|
+
return `${baseName}.alias.vhd` // we want an alias to a vhddirectory
|
|
322
|
+
}
|
|
323
|
+
return `${baseName}.vhd`
|
|
324
|
+
}
|
|
325
|
+
|
|
357
326
|
async listAllVmBackups() {
|
|
358
327
|
const handler = this._handler
|
|
359
328
|
|
|
@@ -498,6 +467,24 @@ class RemoteAdapter {
|
|
|
498
467
|
return backups.sort(compareTimestamp)
|
|
499
468
|
}
|
|
500
469
|
|
|
470
|
+
async writeVhd(path, input, { checksum = true, validator = noop } = {}) {
|
|
471
|
+
const handler = this._handler
|
|
472
|
+
|
|
473
|
+
if (path.endsWith('.alias.vhd')) {
|
|
474
|
+
const dataPath = `${dirname(path)}/data/${uuidv4()}.vhd`
|
|
475
|
+
await createVhdDirectoryFromStream(handler, dataPath, input, {
|
|
476
|
+
concurrency: 16,
|
|
477
|
+
async validator() {
|
|
478
|
+
await input.task
|
|
479
|
+
return validator.apply(this, arguments)
|
|
480
|
+
},
|
|
481
|
+
})
|
|
482
|
+
await VhdAbstract.createAlias(handler, path, dataPath)
|
|
483
|
+
} else {
|
|
484
|
+
await this.outputStream(path, input, { checksum, validator })
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
501
488
|
async outputStream(path, input, { checksum = true, validator = noop } = {}) {
|
|
502
489
|
await this._handler.outputStream(path, input, {
|
|
503
490
|
checksum,
|
|
@@ -509,6 +496,52 @@ class RemoteAdapter {
|
|
|
509
496
|
})
|
|
510
497
|
}
|
|
511
498
|
|
|
499
|
+
async _createSyntheticStream(handler, paths) {
|
|
500
|
+
let disposableVhds = []
|
|
501
|
+
|
|
502
|
+
// if it's a path : open all hierarchy of parent
|
|
503
|
+
if (typeof paths === 'string') {
|
|
504
|
+
let vhd,
|
|
505
|
+
vhdPath = paths
|
|
506
|
+
do {
|
|
507
|
+
const disposable = await openVhd(handler, vhdPath)
|
|
508
|
+
vhd = disposable.value
|
|
509
|
+
disposableVhds.push(disposable)
|
|
510
|
+
vhdPath = resolveRelativeFromFile(vhdPath, vhd.header.parentUnicodeName)
|
|
511
|
+
} while (vhd.footer.diskType !== Constants.DISK_TYPES.DYNAMIC)
|
|
512
|
+
} else {
|
|
513
|
+
// only open the list of path given
|
|
514
|
+
disposableVhds = paths.map(path => openVhd(handler, path))
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// I don't want the vhds to be disposed on return
|
|
518
|
+
// but only when the stream is done ( or failed )
|
|
519
|
+
const disposables = await Disposable.all(disposableVhds)
|
|
520
|
+
const vhds = disposables.value
|
|
521
|
+
|
|
522
|
+
let disposed = false
|
|
523
|
+
const disposeOnce = async () => {
|
|
524
|
+
if (!disposed) {
|
|
525
|
+
disposed = true
|
|
526
|
+
|
|
527
|
+
try {
|
|
528
|
+
await disposables.dispose()
|
|
529
|
+
} catch (error) {
|
|
530
|
+
warn('_createSyntheticStream: failed to dispose VHDs', { error })
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const synthetic = new VhdSynthetic(vhds)
|
|
536
|
+
await synthetic.readHeaderAndFooter()
|
|
537
|
+
await synthetic.readBlockAllocationTable()
|
|
538
|
+
const stream = await synthetic.stream()
|
|
539
|
+
stream.on('end', disposeOnce)
|
|
540
|
+
stream.on('close', disposeOnce)
|
|
541
|
+
stream.on('error', disposeOnce)
|
|
542
|
+
return stream
|
|
543
|
+
}
|
|
544
|
+
|
|
512
545
|
async readDeltaVmBackup(metadata) {
|
|
513
546
|
const handler = this._handler
|
|
514
547
|
const { vbds, vdis, vhds, vifs, vm } = metadata
|
|
@@ -516,7 +549,7 @@ class RemoteAdapter {
|
|
|
516
549
|
|
|
517
550
|
const streams = {}
|
|
518
551
|
await asyncMapSettled(Object.keys(vdis), async id => {
|
|
519
|
-
streams[`${id}.vhd`] = await
|
|
552
|
+
streams[`${id}.vhd`] = await this._createSyntheticStream(handler, join(dir, vhds[id]))
|
|
520
553
|
})
|
|
521
554
|
|
|
522
555
|
return {
|
package/_VmBackup.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
const assert = require('assert')
|
|
2
2
|
const findLast = require('lodash/findLast.js')
|
|
3
|
+
const groupBy = require('lodash/groupBy.js')
|
|
3
4
|
const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
|
|
4
5
|
const keyBy = require('lodash/keyBy.js')
|
|
5
6
|
const mapValues = require('lodash/mapValues.js')
|
|
6
|
-
const { asyncMap
|
|
7
|
+
const { asyncMap } = require('@xen-orchestra/async-map')
|
|
7
8
|
const { createLogger } = require('@xen-orchestra/log')
|
|
8
9
|
const { defer } = require('golike-defer')
|
|
9
10
|
const { formatDateTime } = require('@xen-orchestra/xapi')
|
|
@@ -284,17 +285,28 @@ exports.VmBackup = class VmBackup {
|
|
|
284
285
|
}
|
|
285
286
|
|
|
286
287
|
async _removeUnusedSnapshots() {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
const { scheduleId } = this
|
|
290
|
-
const scheduleSnapshots = this._jobSnapshots.filter(_ => _.other_config['xo:backup:schedule'] === scheduleId)
|
|
291
|
-
|
|
288
|
+
const jobSettings = this.job.settings
|
|
292
289
|
const baseVmRef = this._baseVm?.$ref
|
|
290
|
+
const { config } = this
|
|
291
|
+
const baseSettings = {
|
|
292
|
+
...config.defaultSettings,
|
|
293
|
+
...config.metadata.defaultSettings,
|
|
294
|
+
...jobSettings[''],
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const snapshotsPerSchedule = groupBy(this._jobSnapshots, _ => _.other_config['xo:backup:schedule'])
|
|
293
298
|
const xapi = this._xapi
|
|
294
|
-
await asyncMap(
|
|
295
|
-
|
|
296
|
-
|
|
299
|
+
await asyncMap(Object.entries(snapshotsPerSchedule), ([scheduleId, snapshots]) => {
|
|
300
|
+
const settings = {
|
|
301
|
+
...baseSettings,
|
|
302
|
+
...jobSettings[scheduleId],
|
|
303
|
+
...jobSettings[this.vm.uuid],
|
|
297
304
|
}
|
|
305
|
+
return asyncMap(getOldEntries(settings.snapshotRetention, snapshots), ({ $ref }) => {
|
|
306
|
+
if ($ref !== baseVmRef) {
|
|
307
|
+
return xapi.VM_destroy($ref)
|
|
308
|
+
}
|
|
309
|
+
})
|
|
298
310
|
})
|
|
299
311
|
}
|
|
300
312
|
|
|
@@ -321,13 +333,16 @@ exports.VmBackup = class VmBackup {
|
|
|
321
333
|
|
|
322
334
|
const baseUuidToSrcVdi = new Map()
|
|
323
335
|
await asyncMap(await baseVm.$getDisks(), async baseRef => {
|
|
324
|
-
const snapshotOf = await
|
|
336
|
+
const [baseUuid, snapshotOf] = await Promise.all([
|
|
337
|
+
xapi.getField('VDI', baseRef, 'uuid'),
|
|
338
|
+
xapi.getField('VDI', baseRef, 'snapshot_of'),
|
|
339
|
+
])
|
|
325
340
|
const srcVdi = srcVdis[snapshotOf]
|
|
326
341
|
if (srcVdi !== undefined) {
|
|
327
|
-
baseUuidToSrcVdi.set(
|
|
342
|
+
baseUuidToSrcVdi.set(baseUuid, srcVdi)
|
|
328
343
|
} else {
|
|
329
|
-
debug('
|
|
330
|
-
vdi:
|
|
344
|
+
debug('ignore snapshot VDI because no longer present on VM', {
|
|
345
|
+
vdi: baseUuid,
|
|
331
346
|
})
|
|
332
347
|
}
|
|
333
348
|
})
|
package/_cleanVm.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
const assert = require('assert')
|
|
2
2
|
const sum = require('lodash/sum')
|
|
3
3
|
const { asyncMap } = require('@xen-orchestra/async-map')
|
|
4
|
-
const {
|
|
4
|
+
const { Constants, mergeVhd, openVhd, VhdAbstract, VhdFile } = require('vhd-lib')
|
|
5
5
|
const { dirname, resolve } = require('path')
|
|
6
|
-
const {
|
|
6
|
+
const { DISK_TYPES } = Constants
|
|
7
7
|
const { isMetadataFile, isVhdFile, isXvaFile, isXvaSumFile } = require('./_backupType.js')
|
|
8
8
|
const { limitConcurrency } = require('limit-concurrency-decorator')
|
|
9
9
|
|
|
10
10
|
const { Task } = require('./Task.js')
|
|
11
|
+
const { Disposable } = require('promise-toolbox')
|
|
11
12
|
|
|
12
13
|
// chain is an array of VHDs from child to parent
|
|
13
14
|
//
|
|
@@ -65,12 +66,12 @@ async function mergeVhdChain(chain, { handler, onLog, remove, merge }) {
|
|
|
65
66
|
clearInterval(handle)
|
|
66
67
|
|
|
67
68
|
await Promise.all([
|
|
68
|
-
|
|
69
|
+
VhdAbstract.rename(handler, parent, child),
|
|
69
70
|
asyncMap(children.slice(0, -1), child => {
|
|
70
71
|
onLog(`the VHD ${child} is unused`)
|
|
71
72
|
if (remove) {
|
|
72
73
|
onLog(`deleting unused VHD ${child}`)
|
|
73
|
-
return
|
|
74
|
+
return VhdAbstract.unlink(handler, child)
|
|
74
75
|
}
|
|
75
76
|
}),
|
|
76
77
|
])
|
|
@@ -124,6 +125,8 @@ exports.cleanVm = async function cleanVm(
|
|
|
124
125
|
vmDir,
|
|
125
126
|
{ fixMetadata, remove, merge, mergeLimiter = defaultMergeLimiter, onLog = noop }
|
|
126
127
|
) {
|
|
128
|
+
const limitedMergeVhdChain = mergeLimiter(mergeVhdChain)
|
|
129
|
+
|
|
127
130
|
const handler = this._handler
|
|
128
131
|
|
|
129
132
|
const vhds = new Set()
|
|
@@ -135,53 +138,55 @@ exports.cleanVm = async function cleanVm(
|
|
|
135
138
|
// remove broken VHDs
|
|
136
139
|
await asyncMap(vhdsList.vhds, async path => {
|
|
137
140
|
try {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
141
|
+
await Disposable.use(openVhd(handler, path, { checkSecondFooter: !vhdsList.interruptedVhds.has(path) }), vhd => {
|
|
142
|
+
vhds.add(path)
|
|
143
|
+
if (vhd.footer.diskType === DISK_TYPES.DIFFERENCING) {
|
|
144
|
+
const parent = resolve('/', dirname(path), vhd.header.parentUnicodeName)
|
|
145
|
+
vhdParents[path] = parent
|
|
146
|
+
if (parent in vhdChildren) {
|
|
147
|
+
const error = new Error('this script does not support multiple VHD children')
|
|
148
|
+
error.parent = parent
|
|
149
|
+
error.child1 = vhdChildren[parent]
|
|
150
|
+
error.child2 = path
|
|
151
|
+
throw error // should we throw?
|
|
152
|
+
}
|
|
153
|
+
vhdChildren[parent] = path
|
|
150
154
|
}
|
|
151
|
-
|
|
152
|
-
}
|
|
155
|
+
})
|
|
153
156
|
} catch (error) {
|
|
154
157
|
onLog(`error while checking the VHD with path ${path}`, { error })
|
|
155
158
|
if (error?.code === 'ERR_ASSERTION' && remove) {
|
|
156
159
|
onLog(`deleting broken ${path}`)
|
|
157
|
-
|
|
160
|
+
return VhdAbstract.unlink(handler, path)
|
|
158
161
|
}
|
|
159
162
|
}
|
|
160
163
|
})
|
|
161
164
|
|
|
165
|
+
// @todo : add check for data folder of alias not referenced in a valid alias
|
|
166
|
+
|
|
162
167
|
// remove VHDs with missing ancestors
|
|
163
168
|
{
|
|
164
169
|
const deletions = []
|
|
165
170
|
|
|
166
171
|
// return true if the VHD has been deleted or is missing
|
|
167
|
-
const deleteIfOrphan =
|
|
168
|
-
const parent = vhdParents[
|
|
172
|
+
const deleteIfOrphan = vhdPath => {
|
|
173
|
+
const parent = vhdParents[vhdPath]
|
|
169
174
|
if (parent === undefined) {
|
|
170
175
|
return
|
|
171
176
|
}
|
|
172
177
|
|
|
173
178
|
// no longer needs to be checked
|
|
174
|
-
delete vhdParents[
|
|
179
|
+
delete vhdParents[vhdPath]
|
|
175
180
|
|
|
176
181
|
deleteIfOrphan(parent)
|
|
177
182
|
|
|
178
183
|
if (!vhds.has(parent)) {
|
|
179
|
-
vhds.delete(
|
|
184
|
+
vhds.delete(vhdPath)
|
|
180
185
|
|
|
181
|
-
onLog(`the parent ${parent} of the VHD ${
|
|
186
|
+
onLog(`the parent ${parent} of the VHD ${vhdPath} is missing`)
|
|
182
187
|
if (remove) {
|
|
183
|
-
onLog(`deleting orphan VHD ${
|
|
184
|
-
deletions.push(
|
|
188
|
+
onLog(`deleting orphan VHD ${vhdPath}`)
|
|
189
|
+
deletions.push(VhdAbstract.unlink(handler, vhdPath))
|
|
185
190
|
}
|
|
186
191
|
}
|
|
187
192
|
}
|
|
@@ -251,15 +256,26 @@ exports.cleanVm = async function cleanVm(
|
|
|
251
256
|
const { vhds } = metadata
|
|
252
257
|
return Object.keys(vhds).map(key => resolve('/', vmDir, vhds[key]))
|
|
253
258
|
})()
|
|
254
|
-
|
|
255
259
|
// FIXME: find better approach by keeping as much of the backup as
|
|
256
260
|
// possible (existing disks) even if one disk is missing
|
|
257
261
|
if (linkedVhds.every(_ => vhds.has(_))) {
|
|
258
262
|
linkedVhds.forEach(_ => unusedVhds.delete(_))
|
|
259
263
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
264
|
+
// checking the size of a vhd directory is costly
|
|
265
|
+
// 1 Http Query per 1000 blocks
|
|
266
|
+
// we only check size of all the vhd are VhdFiles
|
|
267
|
+
|
|
268
|
+
const shouldComputeSize = linkedVhds.every(vhd => vhd instanceof VhdFile)
|
|
269
|
+
if (shouldComputeSize) {
|
|
270
|
+
try {
|
|
271
|
+
await Disposable.use(Disposable.all(linkedVhds.map(vhdPath => openVhd(handler, vhdPath))), async vhds => {
|
|
272
|
+
const sizes = await asyncMap(vhds, vhd => vhd.getSize())
|
|
273
|
+
size = sum(sizes)
|
|
274
|
+
})
|
|
275
|
+
} catch (error) {
|
|
276
|
+
onLog(`failed to get size of ${json}`, { error })
|
|
277
|
+
}
|
|
278
|
+
}
|
|
263
279
|
} else {
|
|
264
280
|
onLog(`Some VHDs linked to the metadata ${json} are missing`)
|
|
265
281
|
if (remove) {
|
|
@@ -322,7 +338,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
322
338
|
onLog(`the VHD ${vhd} is unused`)
|
|
323
339
|
if (remove) {
|
|
324
340
|
onLog(`deleting unused VHD ${vhd}`)
|
|
325
|
-
unusedVhdsDeletion.push(
|
|
341
|
+
unusedVhdsDeletion.push(VhdAbstract.unlink(handler, vhd))
|
|
326
342
|
}
|
|
327
343
|
}
|
|
328
344
|
|
|
@@ -343,9 +359,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
343
359
|
}
|
|
344
360
|
|
|
345
361
|
const doMerge = () => {
|
|
346
|
-
const promise = asyncMap(toMerge, async chain => {
|
|
347
|
-
mergeVhdChain(chain, { handler, onLog, remove, merge })
|
|
348
|
-
})
|
|
362
|
+
const promise = asyncMap(toMerge, async chain => limitedMergeVhdChain(chain, { handler, onLog, remove, merge }))
|
|
349
363
|
return merge ? promise.then(sizes => ({ size: sum(sizes) })) : promise
|
|
350
364
|
}
|
|
351
365
|
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
|
10
10
|
},
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.16.1",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.6"
|
|
14
14
|
},
|
|
@@ -16,14 +16,14 @@
|
|
|
16
16
|
"postversion": "npm publish --access public"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@vates/compose": "^2.
|
|
19
|
+
"@vates/compose": "^2.1.0",
|
|
20
20
|
"@vates/disposable": "^0.1.1",
|
|
21
21
|
"@vates/parse-duration": "^0.1.1",
|
|
22
22
|
"@xen-orchestra/async-map": "^0.1.2",
|
|
23
|
-
"@xen-orchestra/fs": "^0.
|
|
23
|
+
"@xen-orchestra/fs": "^0.19.1",
|
|
24
24
|
"@xen-orchestra/log": "^0.3.0",
|
|
25
25
|
"@xen-orchestra/template": "^0.1.0",
|
|
26
|
-
"compare-versions": "^
|
|
26
|
+
"compare-versions": "^4.0.1",
|
|
27
27
|
"d3-time-format": "^3.0.0",
|
|
28
28
|
"end-of-stream": "^1.4.4",
|
|
29
29
|
"fs-extra": "^10.0.0",
|
|
@@ -35,11 +35,12 @@
|
|
|
35
35
|
"promise-toolbox": "^0.20.0",
|
|
36
36
|
"proper-lockfile": "^4.1.2",
|
|
37
37
|
"pump": "^3.0.0",
|
|
38
|
-
"
|
|
38
|
+
"uuid": "^8.3.2",
|
|
39
|
+
"vhd-lib": "^2.0.2",
|
|
39
40
|
"yazl": "^2.5.1"
|
|
40
41
|
},
|
|
41
42
|
"peerDependencies": {
|
|
42
|
-
"@xen-orchestra/xapi": "^0.
|
|
43
|
+
"@xen-orchestra/xapi": "^0.8.4"
|
|
43
44
|
},
|
|
44
45
|
"license": "AGPL-3.0-or-later",
|
|
45
46
|
"author": {
|
|
@@ -3,7 +3,7 @@ const map = require('lodash/map.js')
|
|
|
3
3
|
const mapValues = require('lodash/mapValues.js')
|
|
4
4
|
const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
|
|
5
5
|
const { asyncMap } = require('@xen-orchestra/async-map')
|
|
6
|
-
const { chainVhd, checkVhdChain,
|
|
6
|
+
const { chainVhd, checkVhdChain, openVhd, VhdAbstract } = require('vhd-lib')
|
|
7
7
|
const { createLogger } = require('@xen-orchestra/log')
|
|
8
8
|
const { dirname } = require('path')
|
|
9
9
|
|
|
@@ -16,6 +16,7 @@ const { MixinBackupWriter } = require('./_MixinBackupWriter.js')
|
|
|
16
16
|
const { AbstractDeltaWriter } = require('./_AbstractDeltaWriter.js')
|
|
17
17
|
const { checkVhd } = require('./_checkVhd.js')
|
|
18
18
|
const { packUuid } = require('./_packUuid.js')
|
|
19
|
+
const { Disposable } = require('promise-toolbox')
|
|
19
20
|
|
|
20
21
|
const { warn } = createLogger('xo:backups:DeltaBackupWriter')
|
|
21
22
|
|
|
@@ -37,13 +38,13 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
|
|
|
37
38
|
await asyncMap(vhds, async path => {
|
|
38
39
|
try {
|
|
39
40
|
await checkVhdChain(handler, path)
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
await Disposable.use(
|
|
42
|
+
openVhd(handler, path),
|
|
43
|
+
vhd => (found = found || vhd.footer.uuid.equals(packUuid(baseUuid)))
|
|
44
|
+
)
|
|
44
45
|
} catch (error) {
|
|
45
46
|
warn('checkBaseVdis', { error })
|
|
46
|
-
await ignoreErrors.call(
|
|
47
|
+
await ignoreErrors.call(VhdAbstract.unlink(handler, path))
|
|
47
48
|
}
|
|
48
49
|
})
|
|
49
50
|
} catch (error) {
|
|
@@ -144,7 +145,7 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
|
|
|
144
145
|
// don't do delta for it
|
|
145
146
|
vdi.uuid
|
|
146
147
|
: vdi.$snapshot_of$uuid
|
|
147
|
-
}/${basename}
|
|
148
|
+
}/${adapter.getVhdFileName(basename)}`
|
|
148
149
|
)
|
|
149
150
|
|
|
150
151
|
const metadataFilename = `${backupDir}/${basename}.json`
|
|
@@ -188,7 +189,7 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
|
|
|
188
189
|
await checkVhd(handler, parentPath)
|
|
189
190
|
}
|
|
190
191
|
|
|
191
|
-
await adapter.
|
|
192
|
+
await adapter.writeVhd(path, deltaExport.streams[`${id}.vhd`], {
|
|
192
193
|
// no checksum for VHDs, because they will be invalidated by
|
|
193
194
|
// merges and chainings
|
|
194
195
|
checksum: false,
|
|
@@ -200,11 +201,11 @@ exports.DeltaBackupWriter = class DeltaBackupWriter extends MixinBackupWriter(Ab
|
|
|
200
201
|
}
|
|
201
202
|
|
|
202
203
|
// set the correct UUID in the VHD
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
204
|
+
await Disposable.use(openVhd(handler, path), async vhd => {
|
|
205
|
+
vhd.footer.uuid = packUuid(vdi.uuid)
|
|
206
|
+
await vhd.readBlockAllocationTable() // required by writeFooter()
|
|
207
|
+
await vhd.writeFooter()
|
|
208
|
+
})
|
|
208
209
|
})
|
|
209
210
|
)
|
|
210
211
|
return {
|
package/writers/_checkVhd.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
const
|
|
1
|
+
const openVhd = require('vhd-lib').openVhd
|
|
2
|
+
const Disposable = require('promise-toolbox/Disposable')
|
|
2
3
|
|
|
3
4
|
exports.checkVhd = async function checkVhd(handler, path) {
|
|
4
|
-
await
|
|
5
|
+
await Disposable.use(openVhd(handler, path), () => {})
|
|
5
6
|
}
|