@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 CHANGED
@@ -49,6 +49,7 @@ const DEFAULT_VM_SETTINGS = {
49
49
  timeout: 0,
50
50
  useNbd: false,
51
51
  unconditionalSnapshot: false,
52
+ validateVhdStreams: false,
52
53
  vmTimeout: 0,
53
54
  }
54
55
 
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
- const inputWithSize = await createVhdStreamWithLength(input)
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, expectedSize } = {}) {
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
- return { ...JSON.parse(await this._handler.readFile(path)), _filename: path }
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.power_state === 'Suspended') {
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.34.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.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.3.0",
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.1.0"
55
+ "@xen-orchestra/xapi": "^2.2.0"
56
56
  },
57
57
  "license": "AGPL-3.0-or-later",
58
58
  "author": {