@xen-orchestra/backups 0.34.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 +48 -34
- package/_VmBackup.js +9 -0
- package/_deltaVm.js +2 -2
- package/package.json +4 -4
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
|
|
@@ -681,37 +673,17 @@ class RemoteAdapter {
|
|
|
681
673
|
await VhdAbstract.createAlias(handler, path, dataPath)
|
|
682
674
|
return size
|
|
683
675
|
} else {
|
|
684
|
-
|
|
685
|
-
return this.outputStream(path, inputWithSize, { checksum, validator, expectedSize: inputWithSize.length })
|
|
676
|
+
return this.outputStream(path, input, { checksum, validator })
|
|
686
677
|
}
|
|
687
678
|
}
|
|
688
679
|
|
|
689
|
-
async outputStream(path, input, { checksum = true, validator = noop
|
|
680
|
+
async outputStream(path, input, { checksum = true, validator = noop } = {}) {
|
|
690
681
|
const container = watchStreamSize(input)
|
|
691
|
-
|
|
692
682
|
await this._handler.outputStream(path, input, {
|
|
693
683
|
checksum,
|
|
694
684
|
dirMode: this._dirMode,
|
|
695
685
|
async validator() {
|
|
696
686
|
await input.task
|
|
697
|
-
if (expectedSize !== undefined) {
|
|
698
|
-
// check that we read all the stream
|
|
699
|
-
strictEqual(
|
|
700
|
-
container.size,
|
|
701
|
-
expectedSize,
|
|
702
|
-
`transferred size ${container.size}, expected file size : ${expectedSize}`
|
|
703
|
-
)
|
|
704
|
-
}
|
|
705
|
-
let size
|
|
706
|
-
try {
|
|
707
|
-
size = await this._handler.getSize(path)
|
|
708
|
-
} catch (err) {
|
|
709
|
-
// can fail is the remote is encrypted
|
|
710
|
-
}
|
|
711
|
-
if (size !== undefined) {
|
|
712
|
-
// check that everything is written to disk
|
|
713
|
-
strictEqual(size, container.size, `written size ${size}, transfered size : ${container.size}`)
|
|
714
|
-
}
|
|
715
687
|
return validator.apply(this, arguments)
|
|
716
688
|
},
|
|
717
689
|
})
|
|
@@ -747,7 +719,7 @@ class RemoteAdapter {
|
|
|
747
719
|
|
|
748
720
|
async readDeltaVmBackup(metadata, ignoredVdis) {
|
|
749
721
|
const handler = this._handler
|
|
750
|
-
const { vbds, vhds, vifs, vm } = metadata
|
|
722
|
+
const { vbds, vhds, vifs, vm, vmSnapshot } = metadata
|
|
751
723
|
const dir = dirname(metadata._filename)
|
|
752
724
|
const vdis = ignoredVdis === undefined ? metadata.vdis : pickBy(metadata.vdis, vdi => !ignoredVdis.has(vdi.uuid))
|
|
753
725
|
|
|
@@ -762,7 +734,7 @@ class RemoteAdapter {
|
|
|
762
734
|
vdis,
|
|
763
735
|
version: '1.0.0',
|
|
764
736
|
vifs,
|
|
765
|
-
vm,
|
|
737
|
+
vm: { ...vm, suspend_VDI: vmSnapshot.suspend_VDI },
|
|
766
738
|
}
|
|
767
739
|
}
|
|
768
740
|
|
|
@@ -774,7 +746,49 @@ class RemoteAdapter {
|
|
|
774
746
|
// _filename is a private field used to compute the backup id
|
|
775
747
|
//
|
|
776
748
|
// it's enumerable to make it cacheable
|
|
777
|
-
|
|
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
|
|
778
792
|
}
|
|
779
793
|
}
|
|
780
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,
|
|
@@ -251,6 +255,11 @@ class VmBackup {
|
|
|
251
255
|
Task.info('Transfer data using NBD')
|
|
252
256
|
}
|
|
253
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
|
+
|
|
254
263
|
deltaExport.streams = mapValues(deltaExport.streams, this._throttleStream)
|
|
255
264
|
|
|
256
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.
|
|
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": {
|