@xen-orchestra/backups 0.33.0 → 0.35.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 +1 -0
- package/RemoteAdapter.js +49 -36
- package/_VmBackup.js +14 -0
- package/_deltaVm.js +2 -2
- package/package.json +4 -4
- package/writers/DeltaBackupWriter.js +1 -31
package/Backup.js
CHANGED
package/RemoteAdapter.js
CHANGED
|
@@ -10,14 +10,7 @@ const groupBy = require('lodash/groupBy.js')
|
|
|
10
10
|
const pickBy = require('lodash/pickBy.js')
|
|
11
11
|
const { dirname, join, normalize, resolve } = require('path')
|
|
12
12
|
const { createLogger } = require('@xen-orchestra/log')
|
|
13
|
-
const {
|
|
14
|
-
createVhdDirectoryFromStream,
|
|
15
|
-
createVhdStreamWithLength,
|
|
16
|
-
openVhd,
|
|
17
|
-
VhdAbstract,
|
|
18
|
-
VhdDirectory,
|
|
19
|
-
VhdSynthetic,
|
|
20
|
-
} = require('vhd-lib')
|
|
13
|
+
const { createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdDirectory, VhdSynthetic } = require('vhd-lib')
|
|
21
14
|
const { deduped } = require('@vates/disposable/deduped.js')
|
|
22
15
|
const { decorateMethodsWith } = require('@vates/decorate-with')
|
|
23
16
|
const { compose } = require('@vates/compose')
|
|
@@ -39,7 +32,6 @@ const { watchStreamSize } = require('./_watchStreamSize')
|
|
|
39
32
|
// @todo : this import is marked extraneous , sould be fixed when lib is published
|
|
40
33
|
const { mount } = require('@vates/fuse-vhd')
|
|
41
34
|
const { asyncEach } = require('@vates/async-each')
|
|
42
|
-
const { strictEqual } = require('assert')
|
|
43
35
|
|
|
44
36
|
const DIR_XO_CONFIG_BACKUPS = 'xo-config-backups'
|
|
45
37
|
exports.DIR_XO_CONFIG_BACKUPS = DIR_XO_CONFIG_BACKUPS
|
|
@@ -666,7 +658,7 @@ class RemoteAdapter {
|
|
|
666
658
|
return path
|
|
667
659
|
}
|
|
668
660
|
|
|
669
|
-
async writeVhd(path, input, { checksum = true, validator = noop, writeBlockConcurrency
|
|
661
|
+
async writeVhd(path, input, { checksum = true, validator = noop, writeBlockConcurrency } = {}) {
|
|
670
662
|
const handler = this._handler
|
|
671
663
|
if (this.useVhdDirectory()) {
|
|
672
664
|
const dataPath = `${dirname(path)}/data/${uuidv4()}.vhd`
|
|
@@ -677,42 +669,21 @@ class RemoteAdapter {
|
|
|
677
669
|
await input.task
|
|
678
670
|
return validator.apply(this, arguments)
|
|
679
671
|
},
|
|
680
|
-
nbdClient,
|
|
681
672
|
})
|
|
682
673
|
await VhdAbstract.createAlias(handler, path, dataPath)
|
|
683
674
|
return size
|
|
684
675
|
} else {
|
|
685
|
-
|
|
686
|
-
return this.outputStream(path, inputWithSize, { checksum, validator, expectedSize: inputWithSize.length })
|
|
676
|
+
return this.outputStream(path, input, { checksum, validator })
|
|
687
677
|
}
|
|
688
678
|
}
|
|
689
679
|
|
|
690
|
-
async outputStream(path, input, { checksum = true, validator = noop
|
|
680
|
+
async outputStream(path, input, { checksum = true, validator = noop } = {}) {
|
|
691
681
|
const container = watchStreamSize(input)
|
|
692
|
-
|
|
693
682
|
await this._handler.outputStream(path, input, {
|
|
694
683
|
checksum,
|
|
695
684
|
dirMode: this._dirMode,
|
|
696
685
|
async validator() {
|
|
697
686
|
await input.task
|
|
698
|
-
if (expectedSize !== undefined) {
|
|
699
|
-
// check that we read all the stream
|
|
700
|
-
strictEqual(
|
|
701
|
-
container.size,
|
|
702
|
-
expectedSize,
|
|
703
|
-
`transferred size ${container.size}, expected file size : ${expectedSize}`
|
|
704
|
-
)
|
|
705
|
-
}
|
|
706
|
-
let size
|
|
707
|
-
try {
|
|
708
|
-
size = await this._handler.getSize(path)
|
|
709
|
-
} catch (err) {
|
|
710
|
-
// can fail is the remote is encrypted
|
|
711
|
-
}
|
|
712
|
-
if (size !== undefined) {
|
|
713
|
-
// check that everything is written to disk
|
|
714
|
-
strictEqual(size, container.size, `written size ${size}, transfered size : ${container.size}`)
|
|
715
|
-
}
|
|
716
687
|
return validator.apply(this, arguments)
|
|
717
688
|
},
|
|
718
689
|
})
|
|
@@ -748,7 +719,7 @@ class RemoteAdapter {
|
|
|
748
719
|
|
|
749
720
|
async readDeltaVmBackup(metadata, ignoredVdis) {
|
|
750
721
|
const handler = this._handler
|
|
751
|
-
const { vbds, vhds, vifs, vm } = metadata
|
|
722
|
+
const { vbds, vhds, vifs, vm, vmSnapshot } = metadata
|
|
752
723
|
const dir = dirname(metadata._filename)
|
|
753
724
|
const vdis = ignoredVdis === undefined ? metadata.vdis : pickBy(metadata.vdis, vdi => !ignoredVdis.has(vdi.uuid))
|
|
754
725
|
|
|
@@ -763,7 +734,7 @@ class RemoteAdapter {
|
|
|
763
734
|
vdis,
|
|
764
735
|
version: '1.0.0',
|
|
765
736
|
vifs,
|
|
766
|
-
vm,
|
|
737
|
+
vm: { ...vm, suspend_VDI: vmSnapshot.suspend_VDI },
|
|
767
738
|
}
|
|
768
739
|
}
|
|
769
740
|
|
|
@@ -775,7 +746,49 @@ class RemoteAdapter {
|
|
|
775
746
|
// _filename is a private field used to compute the backup id
|
|
776
747
|
//
|
|
777
748
|
// it's enumerable to make it cacheable
|
|
778
|
-
|
|
749
|
+
const metadata = { ...JSON.parse(await this._handler.readFile(path)), _filename: path }
|
|
750
|
+
|
|
751
|
+
// backups created on XenServer < 7.1 via JSON in XML-RPC transports have boolean values encoded as integers, which make them unusable with more recent XAPIs
|
|
752
|
+
if (typeof metadata.vm.is_a_template === 'number') {
|
|
753
|
+
const properties = {
|
|
754
|
+
vbds: ['bootable', 'unpluggable', 'storage_lock', 'empty', 'currently_attached'],
|
|
755
|
+
vdis: [
|
|
756
|
+
'sharable',
|
|
757
|
+
'read_only',
|
|
758
|
+
'storage_lock',
|
|
759
|
+
'managed',
|
|
760
|
+
'missing',
|
|
761
|
+
'is_a_snapshot',
|
|
762
|
+
'allow_caching',
|
|
763
|
+
'metadata_latest',
|
|
764
|
+
],
|
|
765
|
+
vifs: ['currently_attached', 'MAC_autogenerated'],
|
|
766
|
+
vm: ['is_a_template', 'is_control_domain', 'ha_always_run', 'is_a_snapshot', 'is_snapshot_from_vmpp'],
|
|
767
|
+
vmSnapshot: ['is_a_template', 'is_control_domain', 'ha_always_run', 'is_snapshot_from_vmpp'],
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
function fixBooleans(obj, properties) {
|
|
771
|
+
properties.forEach(property => {
|
|
772
|
+
if (typeof obj[property] === 'number') {
|
|
773
|
+
obj[property] = obj[property] === 1
|
|
774
|
+
}
|
|
775
|
+
})
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
for (const [key, propertiesInKey] of Object.entries(properties)) {
|
|
779
|
+
const value = metadata[key]
|
|
780
|
+
if (value !== undefined) {
|
|
781
|
+
// some properties of the metadata are collections indexed by the opaqueRef
|
|
782
|
+
const isCollection = Object.keys(value).some(subKey => subKey.startsWith('OpaqueRef:'))
|
|
783
|
+
if (isCollection) {
|
|
784
|
+
Object.values(value).forEach(subValue => fixBooleans(subValue, propertiesInKey))
|
|
785
|
+
} else {
|
|
786
|
+
fixBooleans(value, propertiesInKey)
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
return metadata
|
|
779
792
|
}
|
|
780
793
|
}
|
|
781
794
|
|
package/_VmBackup.js
CHANGED
|
@@ -6,11 +6,13 @@ const groupBy = require('lodash/groupBy.js')
|
|
|
6
6
|
const ignoreErrors = require('promise-toolbox/ignoreErrors')
|
|
7
7
|
const keyBy = require('lodash/keyBy.js')
|
|
8
8
|
const mapValues = require('lodash/mapValues.js')
|
|
9
|
+
const vhdStreamValidator = require('vhd-lib/vhdStreamValidator.js')
|
|
9
10
|
const { asyncMap } = require('@xen-orchestra/async-map')
|
|
10
11
|
const { createLogger } = require('@xen-orchestra/log')
|
|
11
12
|
const { decorateMethodsWith } = require('@vates/decorate-with')
|
|
12
13
|
const { defer } = require('golike-defer')
|
|
13
14
|
const { formatDateTime } = require('@xen-orchestra/xapi')
|
|
15
|
+
const { pipeline } = require('node:stream')
|
|
14
16
|
|
|
15
17
|
const { DeltaBackupWriter } = require('./writers/DeltaBackupWriter.js')
|
|
16
18
|
const { DeltaReplicationWriter } = require('./writers/DeltaReplicationWriter.js')
|
|
@@ -44,6 +46,8 @@ const forkDeltaExport = deltaExport =>
|
|
|
44
46
|
},
|
|
45
47
|
})
|
|
46
48
|
|
|
49
|
+
const noop = Function.prototype
|
|
50
|
+
|
|
47
51
|
class VmBackup {
|
|
48
52
|
constructor({
|
|
49
53
|
config,
|
|
@@ -245,7 +249,17 @@ class VmBackup {
|
|
|
245
249
|
const deltaExport = await exportDeltaVm(exportedVm, baseVm, {
|
|
246
250
|
fullVdisRequired,
|
|
247
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
|
+
}
|
|
248
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
|
+
|
|
249
263
|
deltaExport.streams = mapValues(deltaExport.streams, this._throttleStream)
|
|
250
264
|
|
|
251
265
|
const timestamp = Date.now()
|
package/_deltaVm.js
CHANGED
|
@@ -187,11 +187,11 @@ exports.importDeltaVm = defer(async function importDeltaVm(
|
|
|
187
187
|
|
|
188
188
|
// 0. Create suspend_VDI
|
|
189
189
|
let suspendVdi
|
|
190
|
-
if (vmRecord.
|
|
190
|
+
if (vmRecord.suspend_VDI !== undefined && vmRecord.suspend_VDI !== 'OpaqueRef:NULL') {
|
|
191
191
|
const vdi = vdiRecords[vmRecord.suspend_VDI]
|
|
192
192
|
if (vdi === undefined) {
|
|
193
193
|
Task.warning('Suspend VDI not available for this suspended VM', {
|
|
194
|
-
vm: pick(vmRecord, 'uuid', 'name_label'),
|
|
194
|
+
vm: pick(vmRecord, 'uuid', 'name_label', 'suspend_VDI'),
|
|
195
195
|
})
|
|
196
196
|
} else {
|
|
197
197
|
suspendVdi = await xapi.getRecord(
|
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.35.0",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.6"
|
|
14
14
|
},
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@vates/decorate-with": "^2.0.0",
|
|
25
25
|
"@vates/disposable": "^0.1.4",
|
|
26
26
|
"@vates/fuse-vhd": "^1.0.0",
|
|
27
|
-
"@vates/nbd-client": "^1.0
|
|
27
|
+
"@vates/nbd-client": "^1.2.0",
|
|
28
28
|
"@vates/parse-duration": "^0.1.1",
|
|
29
29
|
"@xen-orchestra/async-map": "^0.1.2",
|
|
30
30
|
"@xen-orchestra/fs": "^3.3.4",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"promise-toolbox": "^0.21.0",
|
|
43
43
|
"proper-lockfile": "^4.1.2",
|
|
44
44
|
"uuid": "^9.0.0",
|
|
45
|
-
"vhd-lib": "^4.
|
|
45
|
+
"vhd-lib": "^4.4.0",
|
|
46
46
|
"yazl": "^2.5.1"
|
|
47
47
|
},
|
|
48
48
|
"devDependencies": {
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"tmp": "^0.2.1"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@xen-orchestra/xapi": "^2.
|
|
55
|
+
"@xen-orchestra/xapi": "^2.2.0"
|
|
56
56
|
},
|
|
57
57
|
"license": "AGPL-3.0-or-later",
|
|
58
58
|
"author": {
|
|
@@ -20,9 +20,8 @@ const { AbstractDeltaWriter } = require('./_AbstractDeltaWriter.js')
|
|
|
20
20
|
const { checkVhd } = require('./_checkVhd.js')
|
|
21
21
|
const { packUuid } = require('./_packUuid.js')
|
|
22
22
|
const { Disposable } = require('promise-toolbox')
|
|
23
|
-
const NbdClient = require('@vates/nbd-client')
|
|
24
23
|
|
|
25
|
-
const {
|
|
24
|
+
const { warn } = createLogger('xo:backups:DeltaBackupWriter')
|
|
26
25
|
|
|
27
26
|
class DeltaBackupWriter extends MixinBackupWriter(AbstractDeltaWriter) {
|
|
28
27
|
async checkBaseVdis(baseUuidToSrcVdi) {
|
|
@@ -200,41 +199,12 @@ class DeltaBackupWriter extends MixinBackupWriter(AbstractDeltaWriter) {
|
|
|
200
199
|
await checkVhd(handler, parentPath)
|
|
201
200
|
}
|
|
202
201
|
|
|
203
|
-
const vdiRef = vm.$xapi.getObject(vdi.uuid).$ref
|
|
204
|
-
|
|
205
|
-
let nbdClient
|
|
206
|
-
if (this._backup.config.useNbd && adapter.useVhdDirectory()) {
|
|
207
|
-
debug('useNbd is enabled', { vdi: id, path })
|
|
208
|
-
// get nbd if possible
|
|
209
|
-
try {
|
|
210
|
-
// this will always take the first host in the list
|
|
211
|
-
const [nbdInfo] = await vm.$xapi.call('VDI.get_nbd_info', vdiRef)
|
|
212
|
-
debug('got NBD info', { nbdInfo, vdi: id, path })
|
|
213
|
-
nbdClient = new NbdClient(nbdInfo)
|
|
214
|
-
await nbdClient.connect()
|
|
215
|
-
|
|
216
|
-
// this will inform the xapi that we don't need this anymore
|
|
217
|
-
// and will detach the vdi from dom0
|
|
218
|
-
$defer(() => nbdClient.disconnect())
|
|
219
|
-
|
|
220
|
-
info('NBD client ready', { vdi: id, path })
|
|
221
|
-
Task.info('NBD used')
|
|
222
|
-
} catch (error) {
|
|
223
|
-
Task.warning('NBD configured but unusable', { error })
|
|
224
|
-
nbdClient = undefined
|
|
225
|
-
warn('error connecting to NBD server', { error, vdi: id, path })
|
|
226
|
-
}
|
|
227
|
-
} else {
|
|
228
|
-
debug('useNbd is disabled', { vdi: id, path })
|
|
229
|
-
}
|
|
230
|
-
|
|
231
202
|
transferSize += await adapter.writeVhd(path, deltaExport.streams[`${id}.vhd`], {
|
|
232
203
|
// no checksum for VHDs, because they will be invalidated by
|
|
233
204
|
// merges and chainings
|
|
234
205
|
checksum: false,
|
|
235
206
|
validator: tmpPath => checkVhd(handler, tmpPath),
|
|
236
207
|
writeBlockConcurrency: this._backup.config.writeBlockConcurrency,
|
|
237
|
-
nbdClient,
|
|
238
208
|
})
|
|
239
209
|
|
|
240
210
|
if (isDelta) {
|