@xen-orchestra/backups 0.38.3 → 0.40.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 (65) hide show
  1. package/{Backup.js → Backup.mjs} +4 -6
  2. package/{DurablePartition.js → DurablePartition.mjs} +2 -4
  3. package/{HealthCheckVmBackup.js → HealthCheckVmBackup.mjs} +2 -4
  4. package/{ImportVmBackup.js → ImportVmBackup.mjs} +6 -8
  5. package/{RemoteAdapter.js → RemoteAdapter.mjs} +69 -76
  6. package/{RestoreMetadataBackup.js → RestoreMetadataBackup.mjs} +6 -5
  7. package/{Task.js → Task.mjs} +3 -6
  8. package/_backupType.mjs +4 -0
  9. package/{_backupWorker.js → _backupWorker.mjs} +22 -22
  10. package/{_cancelableMap.js → _cancelableMap.mjs} +3 -5
  11. package/{_cleanVm.js → _cleanVm.mjs} +16 -19
  12. package/_filenameDate.mjs +6 -0
  13. package/{_getOldEntries.js → _getOldEntries.mjs} +1 -3
  14. package/{_getTmpDir.js → _getTmpDir.mjs} +5 -7
  15. package/_getVmBackupDir.mjs +5 -0
  16. package/{_incrementalVm.js → _incrementalVm.mjs} +21 -20
  17. package/{_isValidXva.js → _isValidXva.mjs} +2 -5
  18. package/{_listPartitions.js → _listPartitions.mjs} +6 -9
  19. package/{_lvm.js → _lvm.mjs} +5 -7
  20. package/_runners/{Metadata.js → Metadata.mjs} +10 -12
  21. package/_runners/{VmsRemote.js → VmsRemote.mjs} +12 -14
  22. package/_runners/{VmsXapi.js → VmsXapi.mjs} +14 -15
  23. package/_runners/{_Abstract.js → _Abstract.mjs} +7 -9
  24. package/_runners/{_PoolMetadataBackup.js → _PoolMetadataBackup.mjs} +7 -10
  25. package/_runners/{_RemoteTimeoutError.js → _RemoteTimeoutError.mjs} +1 -3
  26. package/_runners/{_XoMetadataBackup.js → _XoMetadataBackup.mjs} +11 -9
  27. package/_runners/{_createStreamThrottle.js → _createStreamThrottle.mjs} +4 -6
  28. package/_runners/{_forkStreamUnpipe.js → _forkStreamUnpipe.mjs} +4 -5
  29. package/_runners/{_getAdaptersByRemote.js → _getAdaptersByRemote.mjs} +1 -3
  30. package/_runners/_runTask.mjs +5 -0
  31. package/_runners/_vmRunners/{FullRemote.js → FullRemote.mjs} +9 -12
  32. package/_runners/_vmRunners/{FullXapi.js → FullXapi.mjs} +7 -9
  33. package/_runners/_vmRunners/{IncrementalRemote.js → IncrementalRemote.mjs} +11 -12
  34. package/_runners/_vmRunners/{IncrementalXapi.js → IncrementalXapi.mjs} +18 -20
  35. package/_runners/_vmRunners/{_Abstract.js → _Abstract.mjs} +4 -6
  36. package/_runners/_vmRunners/{_AbstractRemote.js → _AbstractRemote.mjs} +6 -6
  37. package/_runners/_vmRunners/{_AbstractXapi.js → _AbstractXapi.mjs} +14 -17
  38. package/_runners/_vmRunners/_forkDeltaExport.mjs +11 -0
  39. package/_runners/_writers/{FullRemoteWriter.js → FullRemoteWriter.mjs} +6 -8
  40. package/_runners/_writers/{FullXapiWriter.js → FullXapiWriter.mjs} +10 -12
  41. package/_runners/_writers/{IncrementalRemoteWriter.js → IncrementalRemoteWriter.mjs} +31 -28
  42. package/_runners/_writers/{IncrementalXapiWriter.js → IncrementalXapiWriter.mjs} +11 -13
  43. package/_runners/_writers/{_AbstractFullWriter.js → _AbstractFullWriter.mjs} +2 -4
  44. package/_runners/_writers/{_AbstractIncrementalWriter.js → _AbstractIncrementalWriter.mjs} +2 -4
  45. package/_runners/_writers/{_AbstractWriter.js → _AbstractWriter.mjs} +3 -5
  46. package/_runners/_writers/{_MixinRemoteWriter.js → _MixinRemoteWriter.mjs} +10 -12
  47. package/_runners/_writers/_MixinXapiWriter.mjs +72 -0
  48. package/_runners/_writers/_checkVhd.mjs +6 -0
  49. package/_runners/_writers/{_listReplicatedVms.js → _listReplicatedVms.mjs} +1 -3
  50. package/_runners/_writers/{_packUuid.js → _packUuid.mjs} +1 -3
  51. package/{_watchStreamSize.js → _watchStreamSize.mjs} +1 -3
  52. package/{extractIdsFromSimplePattern.js → extractIdsFromSimplePattern.mjs} +1 -3
  53. package/{formatVmBackups.js → formatVmBackups.mjs} +3 -5
  54. package/merge-worker/{cli.js → cli.mjs} +9 -11
  55. package/merge-worker/{index.js → index.mjs} +6 -7
  56. package/package.json +10 -8
  57. package/{parseMetadataBackupId.js → parseMetadataBackupId.mjs} +2 -4
  58. package/{runBackupWorker.js → runBackupWorker.mjs} +4 -7
  59. package/_backupType.js +0 -6
  60. package/_filenameDate.js +0 -8
  61. package/_getVmBackupDir.js +0 -8
  62. package/_runners/_runTask.js +0 -6
  63. package/_runners/_vmRunners/_forkDeltaExport.js +0 -12
  64. package/_runners/_writers/_MixinXapiWriter.js +0 -46
  65. package/_runners/_writers/_checkVhd.js +0 -8
@@ -1,10 +1,8 @@
1
- 'use strict'
1
+ import { Metadata } from './_runners/Metadata.mjs'
2
+ import { VmsRemote } from './_runners/VmsRemote.mjs'
3
+ import { VmsXapi } from './_runners/VmsXapi.mjs'
2
4
 
3
- const { Metadata } = require('./_runners/Metadata.js')
4
- const { VmsRemote } = require('./_runners/VmsRemote.js')
5
- const { VmsXapi } = require('./_runners/VmsXapi.js')
6
-
7
- exports.createRunner = function createRunner(opts) {
5
+ export function createRunner(opts) {
8
6
  const { type } = opts.job
9
7
  switch (type) {
10
8
  case 'backup':
@@ -1,8 +1,6 @@
1
- 'use strict'
1
+ import { asyncMap } from '@xen-orchestra/async-map'
2
2
 
3
- const { asyncMap } = require('@xen-orchestra/async-map')
4
-
5
- exports.DurablePartition = class DurablePartition {
3
+ export class DurablePartition {
6
4
  // private resource API is used exceptionally to be able to separate resource creation and release
7
5
  #partitionDisposers = {}
8
6
 
@@ -1,8 +1,6 @@
1
- 'use strict'
1
+ import { Task } from './Task.mjs'
2
2
 
3
- const { Task } = require('./Task')
4
-
5
- exports.HealthCheckVmBackup = class HealthCheckVmBackup {
3
+ export class HealthCheckVmBackup {
6
4
  #restoredVm
7
5
  #timeout
8
6
  #xapi
@@ -1,13 +1,11 @@
1
- 'use strict'
1
+ import assert from 'node:assert'
2
2
 
3
- const assert = require('assert')
3
+ import { formatFilenameDate } from './_filenameDate.mjs'
4
+ import { importIncrementalVm } from './_incrementalVm.mjs'
5
+ import { Task } from './Task.mjs'
6
+ import { watchStreamSize } from './_watchStreamSize.mjs'
4
7
 
5
- const { formatFilenameDate } = require('./_filenameDate.js')
6
- const { importIncrementalVm } = require('./_incrementalVm.js')
7
- const { Task } = require('./Task.js')
8
- const { watchStreamSize } = require('./_watchStreamSize.js')
9
-
10
- exports.ImportVmBackup = class ImportVmBackup {
8
+ export class ImportVmBackup {
11
9
  constructor({ adapter, metadata, srUuid, xapi, settings: { newMacAddresses, mapVdisSrs = {} } = {} }) {
12
10
  this._adapter = adapter
13
11
  this._importIncrementalVmSettings = { newMacAddresses, mapVdisSrs }
@@ -1,43 +1,39 @@
1
- 'use strict'
2
-
3
- const { asyncMap, asyncMapSettled } = require('@xen-orchestra/async-map')
4
- const { synchronized } = require('decorator-synchronized')
5
- const Disposable = require('promise-toolbox/Disposable')
6
- const fromCallback = require('promise-toolbox/fromCallback')
7
- const fromEvent = require('promise-toolbox/fromEvent')
8
- const pDefer = require('promise-toolbox/defer')
9
- const groupBy = require('lodash/groupBy.js')
10
- const pickBy = require('lodash/pickBy.js')
11
- const { dirname, join, normalize, resolve } = require('path')
12
- const { createLogger } = require('@xen-orchestra/log')
13
- const { createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdDirectory, VhdSynthetic } = require('vhd-lib')
14
- const { deduped } = require('@vates/disposable/deduped.js')
15
- const { decorateMethodsWith } = require('@vates/decorate-with')
16
- const { compose } = require('@vates/compose')
17
- const { execFile } = require('child_process')
18
- const { readdir, lstat } = require('fs-extra')
19
- const { v4: uuidv4 } = require('uuid')
20
- const { ZipFile } = require('yazl')
21
- const zlib = require('zlib')
22
-
23
- const { BACKUP_DIR } = require('./_getVmBackupDir.js')
24
- const { cleanVm } = require('./_cleanVm.js')
25
- const { formatFilenameDate } = require('./_filenameDate.js')
26
- const { getTmpDir } = require('./_getTmpDir.js')
27
- const { isMetadataFile } = require('./_backupType.js')
28
- const { isValidXva } = require('./_isValidXva.js')
29
- const { listPartitions, LVM_PARTITION_TYPE } = require('./_listPartitions.js')
30
- const { lvs, pvs } = require('./_lvm.js')
31
- const { watchStreamSize } = require('./_watchStreamSize')
32
- // @todo : this import is marked extraneous , sould be fixed when lib is published
33
- const { mount } = require('@vates/fuse-vhd')
34
- const { asyncEach } = require('@vates/async-each')
35
-
36
- const DIR_XO_CONFIG_BACKUPS = 'xo-config-backups'
37
- exports.DIR_XO_CONFIG_BACKUPS = DIR_XO_CONFIG_BACKUPS
38
-
39
- const DIR_XO_POOL_METADATA_BACKUPS = 'xo-pool-metadata-backups'
40
- exports.DIR_XO_POOL_METADATA_BACKUPS = DIR_XO_POOL_METADATA_BACKUPS
1
+ import { asyncEach } from '@vates/async-each'
2
+ import { asyncMap, asyncMapSettled } from '@xen-orchestra/async-map'
3
+ import { compose } from '@vates/compose'
4
+ import { createLogger } from '@xen-orchestra/log'
5
+ import { createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdDirectory, VhdSynthetic } from 'vhd-lib'
6
+ import { decorateMethodsWith } from '@vates/decorate-with'
7
+ import { deduped } from '@vates/disposable/deduped.js'
8
+ import { dirname, join, resolve } from 'node:path'
9
+ import { execFile } from 'child_process'
10
+ import { mount } from '@vates/fuse-vhd'
11
+ import { readdir, lstat } from 'node:fs/promises'
12
+ import { synchronized } from 'decorator-synchronized'
13
+ import { v4 as uuidv4 } from 'uuid'
14
+ import { ZipFile } from 'yazl'
15
+ import Disposable from 'promise-toolbox/Disposable'
16
+ import fromCallback from 'promise-toolbox/fromCallback'
17
+ import fromEvent from 'promise-toolbox/fromEvent'
18
+ import groupBy from 'lodash/groupBy.js'
19
+ import pDefer from 'promise-toolbox/defer'
20
+ import pickBy from 'lodash/pickBy.js'
21
+ import tar from 'tar'
22
+ import zlib from 'zlib'
23
+
24
+ import { BACKUP_DIR } from './_getVmBackupDir.mjs'
25
+ import { cleanVm } from './_cleanVm.mjs'
26
+ import { formatFilenameDate } from './_filenameDate.mjs'
27
+ import { getTmpDir } from './_getTmpDir.mjs'
28
+ import { isMetadataFile } from './_backupType.mjs'
29
+ import { isValidXva } from './_isValidXva.mjs'
30
+ import { listPartitions, LVM_PARTITION_TYPE } from './_listPartitions.mjs'
31
+ import { lvs, pvs } from './_lvm.mjs'
32
+ import { watchStreamSize } from './_watchStreamSize.mjs'
33
+
34
+ export const DIR_XO_CONFIG_BACKUPS = 'xo-config-backups'
35
+
36
+ export const DIR_XO_POOL_METADATA_BACKUPS = 'xo-pool-metadata-backups'
41
37
 
42
38
  const { debug, warn } = createLogger('xo:backups:RemoteAdapter')
43
39
 
@@ -46,20 +42,23 @@ const compareTimestamp = (a, b) => a.timestamp - b.timestamp
46
42
  const noop = Function.prototype
47
43
 
48
44
  const resolveRelativeFromFile = (file, path) => resolve('/', dirname(file), path).slice(1)
49
-
50
- const resolveSubpath = (root, path) => resolve(root, `.${resolve('/', path)}`)
51
-
52
- async function addDirectory(files, realPath, metadataPath) {
53
- const stats = await lstat(realPath)
54
- if (stats.isDirectory()) {
55
- await asyncMap(await readdir(realPath), file =>
56
- addDirectory(files, realPath + '/' + file, metadataPath + '/' + file)
57
- )
58
- } else if (stats.isFile()) {
59
- files.push({
60
- realPath,
61
- metadataPath,
62
- })
45
+ const makeRelative = path => resolve('/', path).slice(1)
46
+ const resolveSubpath = (root, path) => resolve(root, makeRelative(path))
47
+
48
+ async function addZipEntries(zip, realBasePath, virtualBasePath, relativePaths) {
49
+ for (const relativePath of relativePaths) {
50
+ const realPath = join(realBasePath, relativePath)
51
+ const virtualPath = join(virtualBasePath, relativePath)
52
+
53
+ const stats = await lstat(realPath)
54
+ const { mode, mtime } = stats
55
+ const opts = { mode, mtime }
56
+ if (stats.isDirectory()) {
57
+ zip.addEmptyDirectory(virtualPath, opts)
58
+ await addZipEntries(zip, realPath, virtualPath, await readdir(realPath))
59
+ } else if (stats.isFile()) {
60
+ zip.addFile(realPath, virtualPath, opts)
61
+ }
63
62
  }
64
63
  }
65
64
 
@@ -76,7 +75,7 @@ const debounceResourceFactory = factory =>
76
75
  return this._debounceResource(factory.apply(this, arguments))
77
76
  }
78
77
 
79
- class RemoteAdapter {
78
+ export class RemoteAdapter {
80
79
  constructor(
81
80
  handler,
82
81
  { debounceResource = res => res, dirMode, vhdDirectoryCompression, useGetDiskLegacy = false } = {}
@@ -187,17 +186,6 @@ class RemoteAdapter {
187
186
  })
188
187
  }
189
188
 
190
- async *_usePartitionFiles(diskId, partitionId, paths) {
191
- const path = yield this.getPartition(diskId, partitionId)
192
-
193
- const files = []
194
- await asyncMap(paths, file =>
195
- addDirectory(files, resolveSubpath(path, file), normalize('./' + file).replace(/\/+$/, ''))
196
- )
197
-
198
- return files
199
- }
200
-
201
189
  // check if we will be allowed to merge a a vhd created in this adapter
202
190
  // with the vhd at path `path`
203
191
  async isMergeableParent(packedParentUid, path) {
@@ -214,15 +202,24 @@ class RemoteAdapter {
214
202
  })
215
203
  }
216
204
 
217
- fetchPartitionFiles(diskId, partitionId, paths) {
205
+ fetchPartitionFiles(diskId, partitionId, paths, format) {
218
206
  const { promise, reject, resolve } = pDefer()
219
207
  Disposable.use(
220
208
  async function* () {
221
- const files = yield this._usePartitionFiles(diskId, partitionId, paths)
222
- const zip = new ZipFile()
223
- files.forEach(({ realPath, metadataPath }) => zip.addFile(realPath, metadataPath))
224
- zip.end()
225
- const { outputStream } = zip
209
+ const path = yield this.getPartition(diskId, partitionId)
210
+ let outputStream
211
+
212
+ if (format === 'tgz') {
213
+ outputStream = tar.c({ cwd: path, gzip: true }, paths.map(makeRelative))
214
+ } else if (format === 'zip') {
215
+ const zip = new ZipFile()
216
+ await addZipEntries(zip, path, '', paths.map(makeRelative))
217
+ zip.end()
218
+ ;({ outputStream } = zip)
219
+ } else {
220
+ throw new Error('unsupported format ' + format)
221
+ }
222
+
226
223
  resolve(outputStream)
227
224
  await fromEvent(outputStream, 'end')
228
225
  }.bind(this)
@@ -829,11 +826,7 @@ decorateMethodsWith(RemoteAdapter, {
829
826
  debounceResourceFactory,
830
827
  ]),
831
828
 
832
- _usePartitionFiles: Disposable.factory,
833
-
834
829
  getDisk: compose([Disposable.factory, [deduped, diskId => [diskId]], debounceResourceFactory]),
835
830
 
836
831
  getPartition: Disposable.factory,
837
832
  })
838
-
839
- exports.RemoteAdapter = RemoteAdapter
@@ -1,9 +1,9 @@
1
- 'use strict'
1
+ import { join, resolve } from 'node:path/posix'
2
2
 
3
- const { DIR_XO_POOL_METADATA_BACKUPS } = require('./RemoteAdapter.js')
4
- const { PATH_DB_DUMP } = require('./_runners/_PoolMetadataBackup.js')
3
+ import { DIR_XO_POOL_METADATA_BACKUPS } from './RemoteAdapter.mjs'
4
+ import { PATH_DB_DUMP } from './_runners/_PoolMetadataBackup.mjs'
5
5
 
6
- exports.RestoreMetadataBackup = class RestoreMetadataBackup {
6
+ export class RestoreMetadataBackup {
7
7
  constructor({ backupId, handler, xapi }) {
8
8
  this._backupId = backupId
9
9
  this._handler = handler
@@ -20,7 +20,8 @@ exports.RestoreMetadataBackup = class RestoreMetadataBackup {
20
20
  task: xapi.task_create('Import pool metadata'),
21
21
  })
22
22
  } else {
23
- return String(await handler.readFile(`${backupId}/data.json`))
23
+ const metadata = JSON.parse(await handler.readFile(join(backupId, 'metadata.json')))
24
+ return String(await handler.readFile(resolve(backupId, metadata.data ?? 'data.json')))
24
25
  }
25
26
  }
26
27
  }
@@ -1,7 +1,5 @@
1
- 'use strict'
2
-
3
- const CancelToken = require('promise-toolbox/CancelToken')
4
- const Zone = require('node-zone')
1
+ import CancelToken from 'promise-toolbox/CancelToken'
2
+ import Zone from 'node-zone'
5
3
 
6
4
  const logAfterEnd = log => {
7
5
  const error = new Error('task has already ended')
@@ -30,7 +28,7 @@ const serializeError = error =>
30
28
 
31
29
  const $$task = Symbol('@xen-orchestra/backups/Task')
32
30
 
33
- class Task {
31
+ export class Task {
34
32
  static get cancelToken() {
35
33
  const task = Zone.current.data[$$task]
36
34
  return task !== undefined ? task.#cancelToken : CancelToken.none
@@ -151,7 +149,6 @@ class Task {
151
149
  })
152
150
  }
153
151
  }
154
- exports.Task = Task
155
152
 
156
153
  for (const method of ['info', 'warning']) {
157
154
  Task[method] = (...args) => Zone.current.data[$$task]?.[method](...args)
@@ -0,0 +1,4 @@
1
+ export const isMetadataFile = filename => filename.endsWith('.json')
2
+ export const isVhdFile = filename => filename.endsWith('.vhd')
3
+ export const isXvaFile = filename => filename.endsWith('.xva')
4
+ export const isXvaSumFile = filename => filename.endsWith('.xva.checksum')
@@ -1,25 +1,25 @@
1
- 'use strict'
2
-
3
- const logger = require('@xen-orchestra/log').createLogger('xo:backups:worker')
4
-
5
- require('@xen-orchestra/log/configure').catchGlobalErrors(logger)
6
-
7
- require('@vates/cached-dns.lookup').createCachedLookup().patchGlobal()
8
-
9
- const Disposable = require('promise-toolbox/Disposable')
10
- const ignoreErrors = require('promise-toolbox/ignoreErrors')
11
- const { compose } = require('@vates/compose')
12
- const { createDebounceResource } = require('@vates/disposable/debounceResource.js')
13
- const { decorateMethodsWith } = require('@vates/decorate-with')
14
- const { deduped } = require('@vates/disposable/deduped.js')
15
- const { getHandler } = require('@xen-orchestra/fs')
16
- const { createRunner } = require('./Backup.js')
17
- const { parseDuration } = require('@vates/parse-duration')
18
- const { Xapi } = require('@xen-orchestra/xapi')
19
-
20
- const { RemoteAdapter } = require('./RemoteAdapter.js')
21
- const { Task } = require('./Task.js')
22
-
1
+ import { createLogger } from '@xen-orchestra/log'
2
+ import { catchGlobalErrors } from '@xen-orchestra/log/configure'
3
+
4
+ import Disposable from 'promise-toolbox/Disposable'
5
+ import ignoreErrors from 'promise-toolbox/ignoreErrors'
6
+ import { compose } from '@vates/compose'
7
+ import { createCachedLookup } from '@vates/cached-dns.lookup'
8
+ import { createDebounceResource } from '@vates/disposable/debounceResource.js'
9
+ import { createRunner } from './Backup.mjs'
10
+ import { decorateMethodsWith } from '@vates/decorate-with'
11
+ import { deduped } from '@vates/disposable/deduped.js'
12
+ import { getHandler } from '@xen-orchestra/fs'
13
+ import { parseDuration } from '@vates/parse-duration'
14
+ import { Xapi } from '@xen-orchestra/xapi'
15
+
16
+ import { RemoteAdapter } from './RemoteAdapter.mjs'
17
+ import { Task } from './Task.mjs'
18
+
19
+ createCachedLookup().patchGlobal()
20
+
21
+ const logger = createLogger('xo:backups:worker')
22
+ catchGlobalErrors(logger)
23
23
  const { debug } = logger
24
24
 
25
25
  class BackupWorker {
@@ -1,13 +1,11 @@
1
- 'use strict'
2
-
3
- const cancelable = require('promise-toolbox/cancelable')
4
- const CancelToken = require('promise-toolbox/CancelToken')
1
+ import cancelable from 'promise-toolbox/cancelable'
2
+ import CancelToken from 'promise-toolbox/CancelToken'
5
3
 
6
4
  // Similar to `Promise.all` + `map` but pass a cancel token to the callback
7
5
  //
8
6
  // If any of the executions fails, the cancel token will be triggered and the
9
7
  // first reason will be rejected.
10
- exports.cancelableMap = cancelable(async function cancelableMap($cancelToken, iterable, callback) {
8
+ export const cancelableMap = cancelable(async function cancelableMap($cancelToken, iterable, callback) {
11
9
  const { cancel, token } = CancelToken.source([$cancelToken])
12
10
  try {
13
11
  return await Promise.all(
@@ -1,19 +1,18 @@
1
- 'use strict'
2
-
3
- const sum = require('lodash/sum')
4
- const UUID = require('uuid')
5
- const { asyncMap } = require('@xen-orchestra/async-map')
6
- const { Constants, openVhd, VhdAbstract, VhdFile } = require('vhd-lib')
7
- const { isVhdAlias, resolveVhdAlias } = require('vhd-lib/aliases')
8
- const { dirname, resolve } = require('path')
9
- const { DISK_TYPES } = Constants
10
- const { isMetadataFile, isVhdFile, isXvaFile, isXvaSumFile } = require('./_backupType.js')
11
- const { limitConcurrency } = require('limit-concurrency-decorator')
12
- const { mergeVhdChain } = require('vhd-lib/merge')
1
+ import * as UUID from 'uuid'
2
+ import sum from 'lodash/sum.js'
3
+ import { asyncMap } from '@xen-orchestra/async-map'
4
+ import { Constants, openVhd, VhdAbstract, VhdFile } from 'vhd-lib'
5
+ import { isVhdAlias, resolveVhdAlias } from 'vhd-lib/aliases.js'
6
+ import { dirname, resolve } from 'node:path'
7
+ import { isMetadataFile, isVhdFile, isXvaFile, isXvaSumFile } from './_backupType.mjs'
8
+ import { limitConcurrency } from 'limit-concurrency-decorator'
9
+ import { mergeVhdChain } from 'vhd-lib/merge.js'
10
+
11
+ import { Task } from './Task.mjs'
12
+ import { Disposable } from 'promise-toolbox'
13
+ import handlerPath from '@xen-orchestra/fs/path'
13
14
 
14
- const { Task } = require('./Task.js')
15
- const { Disposable } = require('promise-toolbox')
16
- const handlerPath = require('@xen-orchestra/fs/path')
15
+ const { DISK_TYPES } = Constants
17
16
 
18
17
  // checking the size of a vhd directory is costly
19
18
  // 1 Http Query per 1000 blocks
@@ -117,7 +116,7 @@ const listVhds = async (handler, vmDir, logWarn) => {
117
116
  return { vhds, interruptedVhds, aliases }
118
117
  }
119
118
 
120
- async function checkAliases(
119
+ export async function checkAliases(
121
120
  aliasPaths,
122
121
  targetDataRepository,
123
122
  { handler, logInfo = noop, logWarn = console.warn, remove = false }
@@ -176,11 +175,9 @@ async function checkAliases(
176
175
  })
177
176
  }
178
177
 
179
- exports.checkAliases = checkAliases
180
-
181
178
  const defaultMergeLimiter = limitConcurrency(1)
182
179
 
183
- exports.cleanVm = async function cleanVm(
180
+ export async function cleanVm(
184
181
  vmDir,
185
182
  {
186
183
  fixMetadata,
@@ -0,0 +1,6 @@
1
+ import { utcFormat, utcParse } from 'd3-time-format'
2
+
3
+ // Format a date in ISO 8601 in a safe way to be used in filenames
4
+ // (even on Windows).
5
+ export const formatFilenameDate = utcFormat('%Y%m%dT%H%M%SZ')
6
+ export const parseFilenameDate = utcParse('%Y%m%dT%H%M%SZ')
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
1
  // returns all entries but the last retention-th
4
- exports.getOldEntries = function getOldEntries(retention, entries) {
2
+ export function getOldEntries(retention, entries) {
5
3
  return entries === undefined ? [] : retention > 0 ? entries.slice(0, -retention) : entries
6
4
  }
@@ -1,13 +1,11 @@
1
- 'use strict'
2
-
3
- const Disposable = require('promise-toolbox/Disposable')
4
- const { join } = require('path')
5
- const { mkdir, rmdir } = require('fs-extra')
6
- const { tmpdir } = require('os')
1
+ import Disposable from 'promise-toolbox/Disposable'
2
+ import { join } from 'node:path'
3
+ import { mkdir, rmdir } from 'node:fs/promises'
4
+ import { tmpdir } from 'os'
7
5
 
8
6
  const MAX_ATTEMPTS = 3
9
7
 
10
- exports.getTmpDir = async function getTmpDir() {
8
+ export async function getTmpDir() {
11
9
  for (let i = 0; true; ++i) {
12
10
  const path = join(tmpdir(), Math.random().toString(36).slice(2))
13
11
  try {
@@ -0,0 +1,5 @@
1
+ export const BACKUP_DIR = 'xo-vm-backups'
2
+
3
+ export function getVmBackupDir(uuid) {
4
+ return `${BACKUP_DIR}/${uuid}`
5
+ }
@@ -1,24 +1,22 @@
1
- 'use strict'
1
+ import find from 'lodash/find.js'
2
+ import groupBy from 'lodash/groupBy.js'
3
+ import ignoreErrors from 'promise-toolbox/ignoreErrors'
4
+ import omit from 'lodash/omit.js'
5
+ import { asyncMap } from '@xen-orchestra/async-map'
6
+ import { CancelToken } from 'promise-toolbox'
7
+ import { compareVersions } from 'compare-versions'
8
+ import { createVhdStreamWithLength } from 'vhd-lib'
9
+ import { defer } from 'golike-defer'
2
10
 
3
- const find = require('lodash/find.js')
4
- const groupBy = require('lodash/groupBy.js')
5
- const ignoreErrors = require('promise-toolbox/ignoreErrors')
6
- const omit = require('lodash/omit.js')
7
- const { asyncMap } = require('@xen-orchestra/async-map')
8
- const { CancelToken } = require('promise-toolbox')
9
- const { compareVersions } = require('compare-versions')
10
- const { createVhdStreamWithLength } = require('vhd-lib')
11
- const { defer } = require('golike-defer')
11
+ import { cancelableMap } from './_cancelableMap.mjs'
12
+ import { Task } from './Task.mjs'
13
+ import pick from 'lodash/pick.js'
12
14
 
13
- const { cancelableMap } = require('./_cancelableMap.js')
14
- const { Task } = require('./Task.js')
15
- const pick = require('lodash/pick.js')
15
+ export const TAG_BASE_DELTA = 'xo:base_delta'
16
16
 
17
- const TAG_BASE_DELTA = 'xo:base_delta'
18
- exports.TAG_BASE_DELTA = TAG_BASE_DELTA
17
+ export const TAG_COPY_SRC = 'xo:copy_of'
19
18
 
20
- const TAG_COPY_SRC = 'xo:copy_of'
21
- exports.TAG_COPY_SRC = TAG_COPY_SRC
19
+ const TAG_BACKUP_SR = 'xo:backup:sr'
22
20
 
23
21
  const ensureArray = value => (value === undefined ? [] : Array.isArray(value) ? value : [value])
24
22
  const resolveUuid = async (xapi, cache, uuid, type) => {
@@ -33,7 +31,7 @@ const resolveUuid = async (xapi, cache, uuid, type) => {
33
31
  return ref
34
32
  }
35
33
 
36
- exports.exportIncrementalVm = async function exportIncrementalVm(
34
+ export async function exportIncrementalVm(
37
35
  vm,
38
36
  baseVm,
39
37
  {
@@ -143,7 +141,7 @@ exports.exportIncrementalVm = async function exportIncrementalVm(
143
141
  )
144
142
  }
145
143
 
146
- exports.importIncrementalVm = defer(async function importIncrementalVm(
144
+ export const importIncrementalVm = defer(async function importIncrementalVm(
147
145
  $defer,
148
146
  incrementalVm,
149
147
  sr,
@@ -161,7 +159,10 @@ exports.importIncrementalVm = defer(async function importIncrementalVm(
161
159
  if (detectBase) {
162
160
  const remoteBaseVmUuid = vmRecord.other_config[TAG_BASE_DELTA]
163
161
  if (remoteBaseVmUuid) {
164
- baseVm = find(xapi.objects.all, obj => (obj = obj.other_config) && obj[TAG_COPY_SRC] === remoteBaseVmUuid)
162
+ baseVm = find(
163
+ xapi.objects.all,
164
+ obj => (obj = obj.other_config) && obj[TAG_COPY_SRC] === remoteBaseVmUuid && obj[TAG_BACKUP_SR] === sr.$id
165
+ )
165
166
 
166
167
  if (!baseVm) {
167
168
  throw new Error(`could not find the base VM (copy of ${remoteBaseVmUuid})`)
@@ -1,6 +1,4 @@
1
- 'use strict'
2
-
3
- const assert = require('assert')
1
+ import assert from 'node:assert'
4
2
 
5
3
  const COMPRESSED_MAGIC_NUMBERS = [
6
4
  // https://tools.ietf.org/html/rfc1952.html#page-5
@@ -47,7 +45,7 @@ const isValidTar = async (handler, size, fd) => {
47
45
  }
48
46
 
49
47
  // TODO: find an heuristic for compressed files
50
- async function isValidXva(path) {
48
+ export async function isValidXva(path) {
51
49
  const handler = this._handler
52
50
 
53
51
  // size is longer when encrypted + reading part of an encrypted file is not implemented
@@ -74,6 +72,5 @@ async function isValidXva(path) {
74
72
  return true
75
73
  }
76
74
  }
77
- exports.isValidXva = isValidXva
78
75
 
79
76
  const noop = Function.prototype
@@ -1,9 +1,7 @@
1
- 'use strict'
2
-
3
- const fromCallback = require('promise-toolbox/fromCallback')
4
- const { createLogger } = require('@xen-orchestra/log')
5
- const { createParser } = require('parse-pairs')
6
- const { execFile } = require('child_process')
1
+ import fromCallback from 'promise-toolbox/fromCallback'
2
+ import { createLogger } from '@xen-orchestra/log'
3
+ import { createParser } from 'parse-pairs'
4
+ import { execFile } from 'child_process'
7
5
 
8
6
  const { debug } = createLogger('xo:backups:listPartitions')
9
7
 
@@ -24,8 +22,7 @@ const IGNORED_PARTITION_TYPES = {
24
22
  0x82: true, // swap
25
23
  }
26
24
 
27
- const LVM_PARTITION_TYPE = 0x8e
28
- exports.LVM_PARTITION_TYPE = LVM_PARTITION_TYPE
25
+ export const LVM_PARTITION_TYPE = 0x8e
29
26
 
30
27
  const parsePartxLine = createParser({
31
28
  keyTransform: key => (key === 'UUID' ? 'id' : key.toLowerCase()),
@@ -33,7 +30,7 @@ const parsePartxLine = createParser({
33
30
  })
34
31
 
35
32
  // returns an empty array in case of a non-partitioned disk
36
- exports.listPartitions = async function listPartitions(devicePath) {
33
+ export async function listPartitions(devicePath) {
37
34
  const parts = await fromCallback(execFile, 'partx', [
38
35
  '--bytes',
39
36
  '--output=NR,START,SIZE,NAME,UUID,TYPE',
@@ -1,8 +1,6 @@
1
- 'use strict'
2
-
3
- const fromCallback = require('promise-toolbox/fromCallback')
4
- const { createParser } = require('parse-pairs')
5
- const { execFile } = require('child_process')
1
+ import fromCallback from 'promise-toolbox/fromCallback'
2
+ import { createParser } from 'parse-pairs'
3
+ import { execFile } from 'child_process'
6
4
 
7
5
  // ===================================================================
8
6
 
@@ -29,5 +27,5 @@ const makeFunction =
29
27
  .map(Array.isArray(fields) ? parse : line => parse(line)[fields])
30
28
  }
31
29
 
32
- exports.lvs = makeFunction('lvs')
33
- exports.pvs = makeFunction('pvs')
30
+ export const lvs = makeFunction('lvs')
31
+ export const pvs = makeFunction('pvs')