@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.
Files changed (42) hide show
  1. package/Backup.js +4 -2
  2. package/DurablePartition.js +2 -0
  3. package/ImportVmBackup.js +10 -3
  4. package/RemoteAdapter.js +42 -24
  5. package/RestoreMetadataBackup.js +2 -0
  6. package/Task.js +6 -1
  7. package/_PoolMetadataBackup.js +2 -0
  8. package/_VmBackup.js +20 -4
  9. package/_XoMetadataBackup.js +2 -0
  10. package/_backupType.js +2 -0
  11. package/_backupWorker.js +29 -12
  12. package/_cancelableMap.js +4 -2
  13. package/_cleanVm.js +2 -0
  14. package/_deltaVm.js +41 -14
  15. package/_extractIdsFromSimplePattern.js +2 -0
  16. package/_filenameDate.js +2 -0
  17. package/_forkStreamUnpipe.js +2 -0
  18. package/_getOldEntries.js +2 -0
  19. package/_getTmpDir.js +3 -1
  20. package/_getVmBackupDir.js +2 -0
  21. package/_isValidXva.js +2 -0
  22. package/_listPartitions.js +3 -1
  23. package/_lvm.js +3 -1
  24. package/_watchStreamSize.js +2 -0
  25. package/formatVmBackups.js +2 -0
  26. package/merge-worker/cli.js +23 -2
  27. package/merge-worker/index.js +2 -0
  28. package/package.json +9 -5
  29. package/parseMetadataBackupId.js +2 -0
  30. package/runBackupWorker.js +2 -0
  31. package/writers/DeltaBackupWriter.js +3 -1
  32. package/writers/DeltaReplicationWriter.js +3 -1
  33. package/writers/FullBackupWriter.js +2 -0
  34. package/writers/FullReplicationWriter.js +3 -1
  35. package/writers/_AbstractDeltaWriter.js +2 -0
  36. package/writers/_AbstractFullWriter.js +2 -0
  37. package/writers/_AbstractWriter.js +2 -0
  38. package/writers/_MixinBackupWriter.js +3 -1
  39. package/writers/_MixinReplicationWriter.js +2 -0
  40. package/writers/_checkVhd.js +2 -0
  41. package/writers/_listReplicatedVms.js +2 -0
  42. 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.js')
3
- const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
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
 
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { asyncMap } = require('@xen-orchestra/async-map')
2
4
 
3
5
  exports.DurablePartition = class DurablePartition {
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
- backup = await adapter.readDeltaVmBackup(metadata)
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.js')
3
- const fromCallback = require('promise-toolbox/fromCallback.js')
4
- const fromEvent = require('promise-toolbox/fromEvent.js')
5
- const pDefer = require('promise-toolbox/defer.js')
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
- vhdPath = paths
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, vdis, vhds, vifs, vm } = metadata
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 id => {
596
- streams[`${id}.vhd`] = await this._createSyntheticStream(handler, join(dir, vhds[id]))
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
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { DIR_XO_POOL_METADATA_BACKUPS } = require('./RemoteAdapter.js')
2
4
  const { PATH_DB_DUMP } = require('./_PoolMetadataBackup.js')
3
5
 
package/Task.js CHANGED
@@ -1,4 +1,6 @@
1
- const CancelToken = require('promise-toolbox/CancelToken.js')
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,
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { asyncMap } = require('@xen-orchestra/async-map')
2
4
 
3
5
  const { DIR_XO_POOL_METADATA_BACKUPS } = require('./RemoteAdapter.js')
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.js')
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
- exports.VmBackup = class VmBackup {
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 Error('all targets have failed, step: ' + warnMessage)
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
+ })
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { asyncMap } = require('@xen-orchestra/async-map')
2
4
 
3
5
  const { DIR_XO_CONFIG_BACKUPS } = require('./RemoteAdapter.js')
package/_backupType.js CHANGED
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  exports.isMetadataFile = filename => filename.endsWith('.json')
2
4
  exports.isVhdFile = filename => filename.endsWith('.vhd')
3
5
  exports.isXvaFile = filename => filename.endsWith('.xva')
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.js')
6
- const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
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
- const cancelable = require('promise-toolbox/cancelable.js')
2
- const CancelToken = require('promise-toolbox/CancelToken.js')
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
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const assert = require('assert')
2
4
  const sum = require('lodash/sum')
3
5
  const { asyncMap } = require('@xen-orchestra/async-map')
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.js')
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
- suspendVdi = await xapi.getRecord(
183
- 'VDI',
184
- await xapi.VDI_create({
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
- $defer.onFailure(() => suspendVdi.$destroy())
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: mapVdisSrs[vdi.uuid] ?? sr.$ref,
285
+ SR: mapVdisSrRefs[vdi.uuid] ?? sr.$ref,
259
286
  })
260
287
  )
261
288
  $defer.onFailure(() => newVdi.$destroy())
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  exports.extractIdsFromSimplePattern = function extractIdsFromSimplePattern(pattern) {
2
4
  if (pattern === undefined) {
3
5
  return []
package/_filenameDate.js CHANGED
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { utcFormat, utcParse } = require('d3-time-format')
2
4
 
3
5
  // Format a date in ISO 8601 in a safe way to be used in filenames
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const eos = require('end-of-stream')
2
4
  const { PassThrough } = require('stream')
3
5
 
package/_getOldEntries.js CHANGED
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  // returns all entries but the last retention-th
2
4
  exports.getOldEntries = function getOldEntries(retention, entries) {
3
5
  return entries === undefined ? [] : retention > 0 ? entries.slice(0, -retention) : entries
package/_getTmpDir.js CHANGED
@@ -1,4 +1,6 @@
1
- const Disposable = require('promise-toolbox/Disposable.js')
1
+ 'use strict'
2
+
3
+ const Disposable = require('promise-toolbox/Disposable')
2
4
  const { join } = require('path')
3
5
  const { mkdir, rmdir } = require('fs-extra')
4
6
  const { tmpdir } = require('os')
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const BACKUP_DIR = 'xo-vm-backups'
2
4
  exports.BACKUP_DIR = BACKUP_DIR
3
5
 
package/_isValidXva.js CHANGED
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const assert = require('assert')
2
4
 
3
5
  const COMPRESSED_MAGIC_NUMBERS = [
@@ -1,4 +1,6 @@
1
- const fromCallback = require('promise-toolbox/fromCallback.js')
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
@@ -1,4 +1,6 @@
1
- const fromCallback = require('promise-toolbox/fromCallback.js')
1
+ 'use strict'
2
+
3
+ const fromCallback = require('promise-toolbox/fromCallback')
2
4
  const { createParser } = require('parse-pairs')
3
5
  const { execFile } = require('child_process')
4
6
 
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  exports.watchStreamSize = function watchStreamSize(stream, container = { size: 0 }) {
2
4
  stream.on('data', data => {
3
5
  container.size += data.length
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const mapValues = require('lodash/mapValues.js')
2
4
  const { dirname } = require('path')
3
5
 
@@ -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
- await handler.rename(join(CLEAN_VM_QUEUE, taskFileBasename), taskFile)
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
- await adapter.cleanVm(vmDir, { merge: true, onLog: info, remove: true })
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) {
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { join, resolve } = require('path')
2
4
  const { spawn } = require('child_process')
3
5
  const { check } = require('proper-lockfile')
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.19.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.20.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.20.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.8.5"
47
+ "@xen-orchestra/xapi": "^0.10.0"
44
48
  },
45
49
  "license": "AGPL-3.0-or-later",
46
50
  "author": {
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { DIR_XO_CONFIG_BACKUPS, DIR_XO_POOL_METADATA_BACKUPS } = require('./RemoteAdapter.js')
2
4
 
3
5
  exports.parseMetadataBackupId = function parseMetadataBackupId(backupId) {
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const path = require('path')
2
4
  const { createLogger } = require('@xen-orchestra/log')
3
5
  const { fork } = require('child_process')
@@ -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.js')
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.js')
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,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { formatFilenameDate } = require('../_filenameDate.js')
2
4
  const { getOldEntries } = require('../_getOldEntries.js')
3
5
  const { getVmBackupDir } = require('../_getVmBackupDir.js')
@@ -1,4 +1,6 @@
1
- const ignoreErrors = require('promise-toolbox/ignoreErrors.js')
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,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { AbstractWriter } = require('./_AbstractWriter.js')
2
4
 
3
5
  exports.AbstractDeltaWriter = class AbstractDeltaWriter extends AbstractWriter {
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const { AbstractWriter } = require('./_AbstractWriter.js')
2
4
 
3
5
  exports.AbstractFullWriter = class AbstractFullWriter extends AbstractWriter {
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  exports.AbstractWriter = class AbstractWriter {
2
4
  constructor({ backup, settings }) {
3
5
  this._backup = backup
@@ -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 { BACKUP_DIR, getVmBackupDir } = require('../_getVmBackupDir.js')
6
+ const { getVmBackupDir } = require('../_getVmBackupDir.js')
5
7
  const MergeWorker = require('../merge-worker/index.js')
6
8
  const { formatFilenameDate } = require('../_filenameDate.js')
7
9
 
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  exports.MixinReplicationWriter = (BaseClass = Object) =>
2
4
  class MixinReplicationWriter extends BaseClass {
3
5
  constructor({ sr, ...rest }) {
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const openVhd = require('vhd-lib').openVhd
2
4
  const Disposable = require('promise-toolbox/Disposable')
3
5
 
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const getReplicatedVmDatetime = vm => {
2
4
  const { 'xo:backup:datetime': datetime = vm.name_label.slice(-17, -1) } = vm.other_config
3
5
  return datetime
@@ -1,3 +1,5 @@
1
+ 'use strict'
2
+
1
3
  const PARSE_UUID_RE = /-/g
2
4
 
3
5
  exports.packUuid = function packUuid(uuid) {