@xen-orchestra/backups 0.19.0 → 0.21.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 +4 -2
- package/DurablePartition.js +2 -0
- package/ImportVmBackup.js +10 -3
- package/RemoteAdapter.js +42 -24
- package/RestoreMetadataBackup.js +2 -0
- package/Task.js +6 -1
- package/_PoolMetadataBackup.js +2 -0
- package/_VmBackup.js +20 -4
- package/_XoMetadataBackup.js +2 -0
- package/_backupType.js +2 -0
- package/_backupWorker.js +29 -12
- package/_cancelableMap.js +4 -2
- package/_cleanVm.js +2 -0
- package/_deltaVm.js +41 -14
- package/_extractIdsFromSimplePattern.js +2 -0
- package/_filenameDate.js +2 -0
- package/_forkStreamUnpipe.js +2 -0
- package/_getOldEntries.js +2 -0
- package/_getTmpDir.js +3 -1
- package/_getVmBackupDir.js +2 -0
- package/_isValidXva.js +2 -0
- package/_listPartitions.js +3 -1
- package/_lvm.js +3 -1
- package/_watchStreamSize.js +2 -0
- package/formatVmBackups.js +2 -0
- package/merge-worker/cli.js +23 -2
- package/merge-worker/index.js +2 -0
- package/package.json +9 -5
- package/parseMetadataBackupId.js +2 -0
- package/runBackupWorker.js +2 -0
- package/writers/DeltaBackupWriter.js +3 -1
- package/writers/DeltaReplicationWriter.js +3 -1
- package/writers/FullBackupWriter.js +2 -0
- package/writers/FullReplicationWriter.js +3 -1
- package/writers/_AbstractDeltaWriter.js +2 -0
- package/writers/_AbstractFullWriter.js +2 -0
- package/writers/_AbstractWriter.js +2 -0
- package/writers/_MixinBackupWriter.js +3 -1
- package/writers/_MixinReplicationWriter.js +2 -0
- package/writers/_checkVhd.js +2 -0
- package/writers/_listReplicatedVms.js +2 -0
- package/writers/_packUuid.js +2 -0
package/Backup.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
|
|
2
|
-
const Disposable = require('promise-toolbox/Disposable
|
|
3
|
-
const ignoreErrors = require('promise-toolbox/ignoreErrors
|
|
4
|
+
const Disposable = require('promise-toolbox/Disposable')
|
|
5
|
+
const ignoreErrors = require('promise-toolbox/ignoreErrors')
|
|
4
6
|
const { compileTemplate } = require('@xen-orchestra/template')
|
|
5
7
|
const { limitConcurrency } = require('limit-concurrency-decorator')
|
|
6
8
|
|
package/DurablePartition.js
CHANGED
package/ImportVmBackup.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const assert = require('assert')
|
|
2
4
|
|
|
3
5
|
const { formatFilenameDate } = require('./_filenameDate.js')
|
|
@@ -6,9 +8,9 @@ const { Task } = require('./Task.js')
|
|
|
6
8
|
const { watchStreamSize } = require('./_watchStreamSize.js')
|
|
7
9
|
|
|
8
10
|
exports.ImportVmBackup = class ImportVmBackup {
|
|
9
|
-
constructor({ adapter, metadata, srUuid, xapi, settings: { newMacAddresses } = {} }) {
|
|
11
|
+
constructor({ adapter, metadata, srUuid, xapi, settings: { newMacAddresses, mapVdisSrs } = {} }) {
|
|
10
12
|
this._adapter = adapter
|
|
11
|
-
this._importDeltaVmSettings = { newMacAddresses }
|
|
13
|
+
this._importDeltaVmSettings = { newMacAddresses, mapVdisSrs }
|
|
12
14
|
this._metadata = metadata
|
|
13
15
|
this._srUuid = srUuid
|
|
14
16
|
this._xapi = xapi
|
|
@@ -28,7 +30,12 @@ exports.ImportVmBackup = class ImportVmBackup {
|
|
|
28
30
|
} else {
|
|
29
31
|
assert.strictEqual(metadata.mode, 'delta')
|
|
30
32
|
|
|
31
|
-
|
|
33
|
+
const ignoredVdis = new Set(
|
|
34
|
+
Object.entries(this._importDeltaVmSettings.mapVdisSrs)
|
|
35
|
+
.filter(([_, srUuid]) => srUuid === null)
|
|
36
|
+
.map(([vdiUuid]) => vdiUuid)
|
|
37
|
+
)
|
|
38
|
+
backup = await adapter.readDeltaVmBackup(metadata, ignoredVdis)
|
|
32
39
|
Object.values(backup.streams).forEach(stream => watchStreamSize(stream, sizeContainer))
|
|
33
40
|
}
|
|
34
41
|
|
package/RemoteAdapter.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
|
|
2
|
-
const Disposable = require('promise-toolbox/Disposable
|
|
3
|
-
const fromCallback = require('promise-toolbox/fromCallback
|
|
4
|
-
const fromEvent = require('promise-toolbox/fromEvent
|
|
5
|
-
const pDefer = require('promise-toolbox/defer
|
|
4
|
+
const Disposable = require('promise-toolbox/Disposable')
|
|
5
|
+
const fromCallback = require('promise-toolbox/fromCallback')
|
|
6
|
+
const fromEvent = require('promise-toolbox/fromEvent')
|
|
7
|
+
const pDefer = require('promise-toolbox/defer')
|
|
6
8
|
const groupBy = require('lodash/groupBy.js')
|
|
9
|
+
const pickBy = require('lodash/pickBy.js')
|
|
7
10
|
const { dirname, join, normalize, resolve } = require('path')
|
|
8
11
|
const { createLogger } = require('@xen-orchestra/log')
|
|
9
12
|
const { Constants, createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdDirectory, VhdSynthetic } = require('vhd-lib')
|
|
10
13
|
const { deduped } = require('@vates/disposable/deduped.js')
|
|
14
|
+
const { decorateMethodsWith } = require('@vates/decorate-with')
|
|
15
|
+
const { compose } = require('@vates/compose')
|
|
11
16
|
const { execFile } = require('child_process')
|
|
12
17
|
const { readdir, stat } = require('fs-extra')
|
|
13
18
|
const { v4: uuidv4 } = require('uuid')
|
|
@@ -88,9 +93,6 @@ class RemoteAdapter {
|
|
|
88
93
|
return partition
|
|
89
94
|
}
|
|
90
95
|
|
|
91
|
-
_getLvmLogicalVolumes = Disposable.factory(this._getLvmLogicalVolumes)
|
|
92
|
-
_getLvmLogicalVolumes = deduped(this._getLvmLogicalVolumes, (devicePath, pvId, vgName) => [devicePath, pvId, vgName])
|
|
93
|
-
_getLvmLogicalVolumes = debounceResourceFactory(this._getLvmLogicalVolumes)
|
|
94
96
|
async *_getLvmLogicalVolumes(devicePath, pvId, vgName) {
|
|
95
97
|
yield this._getLvmPhysicalVolume(devicePath, pvId && (await this._findPartition(devicePath, pvId)))
|
|
96
98
|
|
|
@@ -102,9 +104,6 @@ class RemoteAdapter {
|
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
|
|
105
|
-
_getLvmPhysicalVolume = Disposable.factory(this._getLvmPhysicalVolume)
|
|
106
|
-
_getLvmPhysicalVolume = deduped(this._getLvmPhysicalVolume, (devicePath, partition) => [devicePath, partition?.id])
|
|
107
|
-
_getLvmPhysicalVolume = debounceResourceFactory(this._getLvmPhysicalVolume)
|
|
108
107
|
async *_getLvmPhysicalVolume(devicePath, partition) {
|
|
109
108
|
const args = []
|
|
110
109
|
if (partition !== undefined) {
|
|
@@ -125,9 +124,6 @@ class RemoteAdapter {
|
|
|
125
124
|
}
|
|
126
125
|
}
|
|
127
126
|
|
|
128
|
-
_getPartition = Disposable.factory(this._getPartition)
|
|
129
|
-
_getPartition = deduped(this._getPartition, (devicePath, partition) => [devicePath, partition?.id])
|
|
130
|
-
_getPartition = debounceResourceFactory(this._getPartition)
|
|
131
127
|
async *_getPartition(devicePath, partition) {
|
|
132
128
|
const options = ['loop', 'ro']
|
|
133
129
|
|
|
@@ -180,7 +176,6 @@ class RemoteAdapter {
|
|
|
180
176
|
})
|
|
181
177
|
}
|
|
182
178
|
|
|
183
|
-
_usePartitionFiles = Disposable.factory(this._usePartitionFiles)
|
|
184
179
|
async *_usePartitionFiles(diskId, partitionId, paths) {
|
|
185
180
|
const path = yield this.getPartition(diskId, partitionId)
|
|
186
181
|
|
|
@@ -297,9 +292,6 @@ class RemoteAdapter {
|
|
|
297
292
|
return this.#useVhdDirectory()
|
|
298
293
|
}
|
|
299
294
|
|
|
300
|
-
getDisk = Disposable.factory(this.getDisk)
|
|
301
|
-
getDisk = deduped(this.getDisk, diskId => [diskId])
|
|
302
|
-
getDisk = debounceResourceFactory(this.getDisk)
|
|
303
295
|
async *getDisk(diskId) {
|
|
304
296
|
const handler = this._handler
|
|
305
297
|
|
|
@@ -336,7 +328,6 @@ class RemoteAdapter {
|
|
|
336
328
|
// - `<partitionId>`: partitioned disk
|
|
337
329
|
// - `<pvId>/<vgName>/<lvName>`: LVM on a partitioned disk
|
|
338
330
|
// - `/<vgName>/lvName>`: LVM on a raw disk
|
|
339
|
-
getPartition = Disposable.factory(this.getPartition)
|
|
340
331
|
async *getPartition(diskId, partitionId) {
|
|
341
332
|
const devicePath = yield this.getDisk(diskId)
|
|
342
333
|
if (partitionId === undefined) {
|
|
@@ -545,8 +536,8 @@ class RemoteAdapter {
|
|
|
545
536
|
|
|
546
537
|
// if it's a path : open all hierarchy of parent
|
|
547
538
|
if (typeof paths === 'string') {
|
|
548
|
-
let vhd
|
|
549
|
-
|
|
539
|
+
let vhd
|
|
540
|
+
let vhdPath = paths
|
|
550
541
|
do {
|
|
551
542
|
const disposable = await openVhd(handler, vhdPath)
|
|
552
543
|
vhd = disposable.value
|
|
@@ -586,14 +577,15 @@ class RemoteAdapter {
|
|
|
586
577
|
return stream
|
|
587
578
|
}
|
|
588
579
|
|
|
589
|
-
async readDeltaVmBackup(metadata) {
|
|
580
|
+
async readDeltaVmBackup(metadata, ignoredVdis) {
|
|
590
581
|
const handler = this._handler
|
|
591
|
-
const { vbds,
|
|
582
|
+
const { vbds, vhds, vifs, vm } = metadata
|
|
592
583
|
const dir = dirname(metadata._filename)
|
|
584
|
+
const vdis = ignoredVdis === undefined ? metadata.vdis : pickBy(metadata.vdis, vdi => !ignoredVdis.has(vdi.uuid))
|
|
593
585
|
|
|
594
586
|
const streams = {}
|
|
595
|
-
await asyncMapSettled(Object.keys(vdis), async
|
|
596
|
-
streams[`${
|
|
587
|
+
await asyncMapSettled(Object.keys(vdis), async ref => {
|
|
588
|
+
streams[`${ref}.vhd`] = await this._createSyntheticStream(handler, join(dir, vhds[ref]))
|
|
597
589
|
})
|
|
598
590
|
|
|
599
591
|
return {
|
|
@@ -626,4 +618,30 @@ Object.assign(RemoteAdapter.prototype, {
|
|
|
626
618
|
isValidXva,
|
|
627
619
|
})
|
|
628
620
|
|
|
621
|
+
decorateMethodsWith(RemoteAdapter, {
|
|
622
|
+
_getLvmLogicalVolumes: compose([
|
|
623
|
+
Disposable.factory,
|
|
624
|
+
[deduped, (devicePath, pvId, vgName) => [devicePath, pvId, vgName]],
|
|
625
|
+
debounceResourceFactory,
|
|
626
|
+
]),
|
|
627
|
+
|
|
628
|
+
_getLvmPhysicalVolume: compose([
|
|
629
|
+
Disposable.factory,
|
|
630
|
+
[deduped, (devicePath, partition) => [devicePath, partition?.id]],
|
|
631
|
+
debounceResourceFactory,
|
|
632
|
+
]),
|
|
633
|
+
|
|
634
|
+
_getPartition: compose([
|
|
635
|
+
Disposable.factory,
|
|
636
|
+
[deduped, (devicePath, partition) => [devicePath, partition?.id]],
|
|
637
|
+
debounceResourceFactory,
|
|
638
|
+
]),
|
|
639
|
+
|
|
640
|
+
_usePartitionFiles: Disposable.factory,
|
|
641
|
+
|
|
642
|
+
getDisk: compose([Disposable.factory, [deduped, diskId => [diskId]], debounceResourceFactory]),
|
|
643
|
+
|
|
644
|
+
getPartition: Disposable.factory,
|
|
645
|
+
})
|
|
646
|
+
|
|
629
647
|
exports.RemoteAdapter = RemoteAdapter
|
package/RestoreMetadataBackup.js
CHANGED
package/Task.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const CancelToken = require('promise-toolbox/CancelToken')
|
|
2
4
|
const Zone = require('node-zone')
|
|
3
5
|
|
|
4
6
|
const logAfterEnd = () => {
|
|
@@ -7,6 +9,8 @@ const logAfterEnd = () => {
|
|
|
7
9
|
|
|
8
10
|
const noop = Function.prototype
|
|
9
11
|
|
|
12
|
+
const serializeErrors = errors => (Array.isArray(errors) ? errors.map(serializeError) : errors)
|
|
13
|
+
|
|
10
14
|
// Create a serializable object from an error.
|
|
11
15
|
//
|
|
12
16
|
// Otherwise some fields might be non-enumerable and missing from logs.
|
|
@@ -15,6 +19,7 @@ const serializeError = error =>
|
|
|
15
19
|
? {
|
|
16
20
|
...error, // Copy enumerable properties.
|
|
17
21
|
code: error.code,
|
|
22
|
+
errors: serializeErrors(error.errors), // supports AggregateError
|
|
18
23
|
message: error.message,
|
|
19
24
|
name: error.name,
|
|
20
25
|
stack: error.stack,
|
package/_PoolMetadataBackup.js
CHANGED
package/_VmBackup.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const assert = require('assert')
|
|
2
4
|
const findLast = require('lodash/findLast.js')
|
|
3
5
|
const groupBy = require('lodash/groupBy.js')
|
|
4
|
-
const ignoreErrors = require('promise-toolbox/ignoreErrors
|
|
6
|
+
const ignoreErrors = require('promise-toolbox/ignoreErrors')
|
|
5
7
|
const keyBy = require('lodash/keyBy.js')
|
|
6
8
|
const mapValues = require('lodash/mapValues.js')
|
|
7
9
|
const { asyncMap } = require('@xen-orchestra/async-map')
|
|
8
10
|
const { createLogger } = require('@xen-orchestra/log')
|
|
11
|
+
const { decorateMethodsWith } = require('@vates/decorate-with')
|
|
9
12
|
const { defer } = require('golike-defer')
|
|
10
13
|
const { formatDateTime } = require('@xen-orchestra/xapi')
|
|
11
14
|
|
|
@@ -21,6 +24,13 @@ const { watchStreamSize } = require('./_watchStreamSize.js')
|
|
|
21
24
|
|
|
22
25
|
const { debug, warn } = createLogger('xo:backups:VmBackup')
|
|
23
26
|
|
|
27
|
+
class AggregateError extends Error {
|
|
28
|
+
constructor(errors, message) {
|
|
29
|
+
super(message)
|
|
30
|
+
this.errors = errors
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
24
34
|
const asyncEach = async (iterable, fn, thisArg = iterable) => {
|
|
25
35
|
for (const item of iterable) {
|
|
26
36
|
await fn.call(thisArg, item)
|
|
@@ -34,7 +44,7 @@ const forkDeltaExport = deltaExport =>
|
|
|
34
44
|
},
|
|
35
45
|
})
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
class VmBackup {
|
|
38
48
|
constructor({ config, getSnapshotNameLabel, job, remoteAdapters, remotes, schedule, settings, srs, vm }) {
|
|
39
49
|
if (vm.other_config['xo:backup:job'] === job.id && 'start' in vm.blocked_operations) {
|
|
40
50
|
// don't match replicated VMs created by this very job otherwise they
|
|
@@ -125,16 +135,18 @@ exports.VmBackup = class VmBackup {
|
|
|
125
135
|
return
|
|
126
136
|
}
|
|
127
137
|
|
|
138
|
+
const errors = []
|
|
128
139
|
await (parallel ? asyncMap : asyncEach)(writers, async function (writer) {
|
|
129
140
|
try {
|
|
130
141
|
await fn(writer)
|
|
131
142
|
} catch (error) {
|
|
143
|
+
errors.push(error)
|
|
132
144
|
this.delete(writer)
|
|
133
145
|
warn(warnMessage, { error, writer: writer.constructor.name })
|
|
134
146
|
}
|
|
135
147
|
})
|
|
136
148
|
if (writers.size === 0) {
|
|
137
|
-
throw new
|
|
149
|
+
throw new AggregateError(errors, 'all targets have failed, step: ' + warnMessage)
|
|
138
150
|
}
|
|
139
151
|
}
|
|
140
152
|
|
|
@@ -385,7 +397,6 @@ exports.VmBackup = class VmBackup {
|
|
|
385
397
|
this._fullVdisRequired = fullVdisRequired
|
|
386
398
|
}
|
|
387
399
|
|
|
388
|
-
run = defer(this.run)
|
|
389
400
|
async run($defer) {
|
|
390
401
|
const settings = this._settings
|
|
391
402
|
assert(
|
|
@@ -433,3 +444,8 @@ exports.VmBackup = class VmBackup {
|
|
|
433
444
|
}
|
|
434
445
|
}
|
|
435
446
|
}
|
|
447
|
+
exports.VmBackup = VmBackup
|
|
448
|
+
|
|
449
|
+
decorateMethodsWith(VmBackup, {
|
|
450
|
+
run: defer,
|
|
451
|
+
})
|
package/_XoMetadataBackup.js
CHANGED
package/_backupType.js
CHANGED
package/_backupWorker.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
require('@xen-orchestra/log/configure.js').catchGlobalErrors(
|
|
2
4
|
require('@xen-orchestra/log').createLogger('xo:backups:worker')
|
|
3
5
|
)
|
|
4
6
|
|
|
5
|
-
const Disposable = require('promise-toolbox/Disposable
|
|
6
|
-
const ignoreErrors = require('promise-toolbox/ignoreErrors
|
|
7
|
+
const Disposable = require('promise-toolbox/Disposable')
|
|
8
|
+
const ignoreErrors = require('promise-toolbox/ignoreErrors')
|
|
7
9
|
const { compose } = require('@vates/compose')
|
|
8
10
|
const { createDebounceResource } = require('@vates/disposable/debounceResource.js')
|
|
11
|
+
const { decorateMethodsWith } = require('@vates/decorate-with')
|
|
9
12
|
const { deduped } = require('@vates/disposable/deduped.js')
|
|
10
13
|
const { getHandler } = require('@xen-orchestra/fs')
|
|
11
14
|
const { parseDuration } = require('@vates/parse-duration')
|
|
@@ -58,11 +61,6 @@ class BackupWorker {
|
|
|
58
61
|
}).run()
|
|
59
62
|
}
|
|
60
63
|
|
|
61
|
-
getAdapter = Disposable.factory(this.getAdapter)
|
|
62
|
-
getAdapter = deduped(this.getAdapter, remote => [remote.url])
|
|
63
|
-
getAdapter = compose(this.getAdapter, function (resource) {
|
|
64
|
-
return this.debounceResource(resource)
|
|
65
|
-
})
|
|
66
64
|
async *getAdapter(remote) {
|
|
67
65
|
const handler = getHandler(remote, this.#remoteOptions)
|
|
68
66
|
await handler.sync()
|
|
@@ -77,11 +75,6 @@ class BackupWorker {
|
|
|
77
75
|
}
|
|
78
76
|
}
|
|
79
77
|
|
|
80
|
-
getXapi = Disposable.factory(this.getXapi)
|
|
81
|
-
getXapi = deduped(this.getXapi, ({ url }) => [url])
|
|
82
|
-
getXapi = compose(this.getXapi, function (resource) {
|
|
83
|
-
return this.debounceResource(resource)
|
|
84
|
-
})
|
|
85
78
|
async *getXapi({ credentials: { username: user, password }, ...opts }) {
|
|
86
79
|
const xapi = new Xapi({
|
|
87
80
|
...this.#xapiOptions,
|
|
@@ -103,6 +96,30 @@ class BackupWorker {
|
|
|
103
96
|
}
|
|
104
97
|
}
|
|
105
98
|
|
|
99
|
+
decorateMethodsWith(BackupWorker, {
|
|
100
|
+
getAdapter: compose([
|
|
101
|
+
Disposable.factory,
|
|
102
|
+
[deduped, remote => [remote.url]],
|
|
103
|
+
[
|
|
104
|
+
compose,
|
|
105
|
+
function (resource) {
|
|
106
|
+
return this.debounceResource(resource)
|
|
107
|
+
},
|
|
108
|
+
],
|
|
109
|
+
]),
|
|
110
|
+
|
|
111
|
+
getXapi: compose([
|
|
112
|
+
Disposable.factory,
|
|
113
|
+
[deduped, xapi => [xapi.url]],
|
|
114
|
+
[
|
|
115
|
+
compose,
|
|
116
|
+
function (resource) {
|
|
117
|
+
return this.debounceResource(resource)
|
|
118
|
+
},
|
|
119
|
+
],
|
|
120
|
+
]),
|
|
121
|
+
})
|
|
122
|
+
|
|
106
123
|
// Received message:
|
|
107
124
|
//
|
|
108
125
|
// Message {
|
package/_cancelableMap.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const cancelable = require('promise-toolbox/cancelable')
|
|
4
|
+
const CancelToken = require('promise-toolbox/CancelToken')
|
|
3
5
|
|
|
4
6
|
// Similar to `Promise.all` + `map` but pass a cancel token to the callback
|
|
5
7
|
//
|
package/_cleanVm.js
CHANGED
package/_deltaVm.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const compareVersions = require('compare-versions')
|
|
2
4
|
const find = require('lodash/find.js')
|
|
3
5
|
const groupBy = require('lodash/groupBy.js')
|
|
4
|
-
const ignoreErrors = require('promise-toolbox/ignoreErrors
|
|
6
|
+
const ignoreErrors = require('promise-toolbox/ignoreErrors')
|
|
5
7
|
const omit = require('lodash/omit.js')
|
|
6
8
|
const { asyncMap } = require('@xen-orchestra/async-map')
|
|
7
9
|
const { CancelToken } = require('promise-toolbox')
|
|
@@ -9,6 +11,8 @@ const { createVhdStreamWithLength } = require('vhd-lib')
|
|
|
9
11
|
const { defer } = require('golike-defer')
|
|
10
12
|
|
|
11
13
|
const { cancelableMap } = require('./_cancelableMap.js')
|
|
14
|
+
const { Task } = require('./Task.js')
|
|
15
|
+
const { pick } = require('lodash')
|
|
12
16
|
|
|
13
17
|
const TAG_BASE_DELTA = 'xo:base_delta'
|
|
14
18
|
exports.TAG_BASE_DELTA = TAG_BASE_DELTA
|
|
@@ -17,6 +21,17 @@ const TAG_COPY_SRC = 'xo:copy_of'
|
|
|
17
21
|
exports.TAG_COPY_SRC = TAG_COPY_SRC
|
|
18
22
|
|
|
19
23
|
const ensureArray = value => (value === undefined ? [] : Array.isArray(value) ? value : [value])
|
|
24
|
+
const resolveUuid = async (xapi, cache, uuid, type) => {
|
|
25
|
+
if (uuid == null) {
|
|
26
|
+
return uuid
|
|
27
|
+
}
|
|
28
|
+
let ref = cache.get(uuid)
|
|
29
|
+
if (ref === undefined) {
|
|
30
|
+
ref = await xapi.call(`${type}.get_by_uuid`, uuid)
|
|
31
|
+
cache.set(uuid, ref)
|
|
32
|
+
}
|
|
33
|
+
return ref
|
|
34
|
+
}
|
|
20
35
|
|
|
21
36
|
exports.exportDeltaVm = async function exportDeltaVm(
|
|
22
37
|
vm,
|
|
@@ -165,6 +180,12 @@ exports.importDeltaVm = defer(async function importDeltaVm(
|
|
|
165
180
|
}
|
|
166
181
|
}
|
|
167
182
|
|
|
183
|
+
const cache = new Map()
|
|
184
|
+
const mapVdisSrRefs = {}
|
|
185
|
+
for (const [vdiUuid, srUuid] of Object.entries(mapVdisSrs)) {
|
|
186
|
+
mapVdisSrRefs[vdiUuid] = await resolveUuid(xapi, cache, srUuid, 'SR')
|
|
187
|
+
}
|
|
188
|
+
|
|
168
189
|
const baseVdis = {}
|
|
169
190
|
baseVm &&
|
|
170
191
|
baseVm.$VBDs.forEach(vbd => {
|
|
@@ -179,19 +200,25 @@ exports.importDeltaVm = defer(async function importDeltaVm(
|
|
|
179
200
|
let suspendVdi
|
|
180
201
|
if (vmRecord.power_state === 'Suspended') {
|
|
181
202
|
const vdi = vdiRecords[vmRecord.suspend_VDI]
|
|
182
|
-
|
|
183
|
-
'VDI',
|
|
184
|
-
|
|
185
|
-
...vdi,
|
|
186
|
-
other_config: {
|
|
187
|
-
...vdi.other_config,
|
|
188
|
-
[TAG_BASE_DELTA]: undefined,
|
|
189
|
-
[TAG_COPY_SRC]: vdi.uuid,
|
|
190
|
-
},
|
|
191
|
-
sr: mapVdisSrs[vdi.uuid] ?? sr.$ref,
|
|
203
|
+
if (vdi === undefined) {
|
|
204
|
+
Task.warning('Suspend VDI not available for this suspended VM', {
|
|
205
|
+
vm: pick(vmRecord, 'uuid', 'name_label'),
|
|
192
206
|
})
|
|
193
|
-
|
|
194
|
-
|
|
207
|
+
} else {
|
|
208
|
+
suspendVdi = await xapi.getRecord(
|
|
209
|
+
'VDI',
|
|
210
|
+
await xapi.VDI_create({
|
|
211
|
+
...vdi,
|
|
212
|
+
other_config: {
|
|
213
|
+
...vdi.other_config,
|
|
214
|
+
[TAG_BASE_DELTA]: undefined,
|
|
215
|
+
[TAG_COPY_SRC]: vdi.uuid,
|
|
216
|
+
},
|
|
217
|
+
sr: mapVdisSrRefs[vdi.uuid] ?? sr.$ref,
|
|
218
|
+
})
|
|
219
|
+
)
|
|
220
|
+
$defer.onFailure(() => suspendVdi.$destroy())
|
|
221
|
+
}
|
|
195
222
|
}
|
|
196
223
|
|
|
197
224
|
// 1. Create the VM.
|
|
@@ -255,7 +282,7 @@ exports.importDeltaVm = defer(async function importDeltaVm(
|
|
|
255
282
|
[TAG_BASE_DELTA]: undefined,
|
|
256
283
|
[TAG_COPY_SRC]: vdi.uuid,
|
|
257
284
|
},
|
|
258
|
-
SR:
|
|
285
|
+
SR: mapVdisSrRefs[vdi.uuid] ?? sr.$ref,
|
|
259
286
|
})
|
|
260
287
|
)
|
|
261
288
|
$defer.onFailure(() => newVdi.$destroy())
|
package/_filenameDate.js
CHANGED
package/_forkStreamUnpipe.js
CHANGED
package/_getOldEntries.js
CHANGED
package/_getTmpDir.js
CHANGED
package/_getVmBackupDir.js
CHANGED
package/_isValidXva.js
CHANGED
package/_listPartitions.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const fromCallback = require('promise-toolbox/fromCallback')
|
|
2
4
|
const { createLogger } = require('@xen-orchestra/log')
|
|
3
5
|
const { createParser } = require('parse-pairs')
|
|
4
6
|
const { execFile } = require('child_process')
|
package/_lvm.js
CHANGED
package/_watchStreamSize.js
CHANGED
package/formatVmBackups.js
CHANGED
package/merge-worker/cli.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
+
'use strict'
|
|
4
|
+
|
|
3
5
|
const { catchGlobalErrors } = require('@xen-orchestra/log/configure.js')
|
|
4
6
|
const { createLogger } = require('@xen-orchestra/log')
|
|
5
7
|
const { getSyncedHandler } = require('@xen-orchestra/fs')
|
|
@@ -41,13 +43,32 @@ const main = Disposable.wrap(async function* main(args) {
|
|
|
41
43
|
let taskFiles
|
|
42
44
|
while ((taskFiles = await listRetry()) !== undefined) {
|
|
43
45
|
const taskFileBasename = min(taskFiles)
|
|
46
|
+
const previousTaskFile = join(CLEAN_VM_QUEUE, taskFileBasename)
|
|
44
47
|
const taskFile = join(CLEAN_VM_QUEUE, '_' + taskFileBasename)
|
|
45
48
|
|
|
46
49
|
// move this task to the end
|
|
47
|
-
|
|
50
|
+
try {
|
|
51
|
+
await handler.rename(previousTaskFile, taskFile)
|
|
52
|
+
} catch (error) {
|
|
53
|
+
// this error occurs if the task failed too many times (i.e. too many `_` prefixes)
|
|
54
|
+
// there is nothing more that can be done
|
|
55
|
+
if (error.code === 'ENAMETOOLONG') {
|
|
56
|
+
await handler.unlink(previousTaskFile)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
throw error
|
|
60
|
+
}
|
|
61
|
+
|
|
48
62
|
try {
|
|
49
63
|
const vmDir = getVmBackupDir(String(await handler.readFile(taskFile)))
|
|
50
|
-
|
|
64
|
+
try {
|
|
65
|
+
await adapter.cleanVm(vmDir, { merge: true, onLog: info, remove: true })
|
|
66
|
+
} catch (error) {
|
|
67
|
+
// consider the clean successful if the VM dir is missing
|
|
68
|
+
if (error.code !== 'ENOENT') {
|
|
69
|
+
throw error
|
|
70
|
+
}
|
|
71
|
+
}
|
|
51
72
|
|
|
52
73
|
handler.unlink(taskFile).catch(error => warn('deleting task failure', { error }))
|
|
53
74
|
} catch (error) {
|
package/merge-worker/index.js
CHANGED
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.21.0",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.6"
|
|
14
14
|
},
|
|
@@ -17,10 +17,11 @@
|
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
19
|
"@vates/compose": "^2.1.0",
|
|
20
|
+
"@vates/decorate-with": "^2.0.0",
|
|
20
21
|
"@vates/disposable": "^0.1.1",
|
|
21
22
|
"@vates/parse-duration": "^0.1.1",
|
|
22
23
|
"@xen-orchestra/async-map": "^0.1.2",
|
|
23
|
-
"@xen-orchestra/fs": "^0.
|
|
24
|
+
"@xen-orchestra/fs": "^1.0.0",
|
|
24
25
|
"@xen-orchestra/log": "^0.3.0",
|
|
25
26
|
"@xen-orchestra/template": "^0.1.0",
|
|
26
27
|
"compare-versions": "^4.0.1",
|
|
@@ -32,15 +33,18 @@
|
|
|
32
33
|
"lodash": "^4.17.20",
|
|
33
34
|
"node-zone": "^0.4.0",
|
|
34
35
|
"parse-pairs": "^1.1.0",
|
|
35
|
-
"promise-toolbox": "^0.
|
|
36
|
+
"promise-toolbox": "^0.21.0",
|
|
36
37
|
"proper-lockfile": "^4.1.2",
|
|
37
|
-
"pump": "^3.0.0",
|
|
38
38
|
"uuid": "^8.3.2",
|
|
39
39
|
"vhd-lib": "^3.1.0",
|
|
40
40
|
"yazl": "^2.5.1"
|
|
41
41
|
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"rimraf": "^3.0.2",
|
|
44
|
+
"tmp": "^0.2.1"
|
|
45
|
+
},
|
|
42
46
|
"peerDependencies": {
|
|
43
|
-
"@xen-orchestra/xapi": "^0.
|
|
47
|
+
"@xen-orchestra/xapi": "^0.10.0"
|
|
44
48
|
},
|
|
45
49
|
"license": "AGPL-3.0-or-later",
|
|
46
50
|
"author": {
|
package/parseMetadataBackupId.js
CHANGED
package/runBackupWorker.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const assert = require('assert')
|
|
2
4
|
const map = require('lodash/map.js')
|
|
3
5
|
const mapValues = require('lodash/mapValues.js')
|
|
4
|
-
const ignoreErrors = require('promise-toolbox/ignoreErrors
|
|
6
|
+
const ignoreErrors = require('promise-toolbox/ignoreErrors')
|
|
5
7
|
const { asyncMap } = require('@xen-orchestra/async-map')
|
|
6
8
|
const { chainVhd, checkVhdChain, openVhd, VhdAbstract } = require('vhd-lib')
|
|
7
9
|
const { createLogger } = require('@xen-orchestra/log')
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
|
|
2
|
-
const ignoreErrors = require('promise-toolbox/ignoreErrors
|
|
4
|
+
const ignoreErrors = require('promise-toolbox/ignoreErrors')
|
|
3
5
|
const { formatDateTime } = require('@xen-orchestra/xapi')
|
|
4
6
|
|
|
5
7
|
const { formatFilenameDate } = require('../_filenameDate.js')
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const ignoreErrors = require('promise-toolbox/ignoreErrors')
|
|
2
4
|
const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
|
|
3
5
|
const { formatDateTime } = require('@xen-orchestra/xapi')
|
|
4
6
|
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
1
3
|
const { createLogger } = require('@xen-orchestra/log')
|
|
2
4
|
const { join } = require('path')
|
|
3
5
|
|
|
4
|
-
const {
|
|
6
|
+
const { getVmBackupDir } = require('../_getVmBackupDir.js')
|
|
5
7
|
const MergeWorker = require('../merge-worker/index.js')
|
|
6
8
|
const { formatFilenameDate } = require('../_filenameDate.js')
|
|
7
9
|
|
package/writers/_checkVhd.js
CHANGED