@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.
- package/{Backup.js → Backup.mjs} +4 -6
- package/{DurablePartition.js → DurablePartition.mjs} +2 -4
- package/{HealthCheckVmBackup.js → HealthCheckVmBackup.mjs} +2 -4
- package/{ImportVmBackup.js → ImportVmBackup.mjs} +6 -8
- package/{RemoteAdapter.js → RemoteAdapter.mjs} +69 -76
- package/{RestoreMetadataBackup.js → RestoreMetadataBackup.mjs} +6 -5
- package/{Task.js → Task.mjs} +3 -6
- package/_backupType.mjs +4 -0
- package/{_backupWorker.js → _backupWorker.mjs} +22 -22
- package/{_cancelableMap.js → _cancelableMap.mjs} +3 -5
- package/{_cleanVm.js → _cleanVm.mjs} +16 -19
- package/_filenameDate.mjs +6 -0
- package/{_getOldEntries.js → _getOldEntries.mjs} +1 -3
- package/{_getTmpDir.js → _getTmpDir.mjs} +5 -7
- package/_getVmBackupDir.mjs +5 -0
- package/{_incrementalVm.js → _incrementalVm.mjs} +21 -20
- package/{_isValidXva.js → _isValidXva.mjs} +2 -5
- package/{_listPartitions.js → _listPartitions.mjs} +6 -9
- package/{_lvm.js → _lvm.mjs} +5 -7
- package/_runners/{Metadata.js → Metadata.mjs} +10 -12
- package/_runners/{VmsRemote.js → VmsRemote.mjs} +12 -14
- package/_runners/{VmsXapi.js → VmsXapi.mjs} +14 -15
- package/_runners/{_Abstract.js → _Abstract.mjs} +7 -9
- package/_runners/{_PoolMetadataBackup.js → _PoolMetadataBackup.mjs} +7 -10
- package/_runners/{_RemoteTimeoutError.js → _RemoteTimeoutError.mjs} +1 -3
- package/_runners/{_XoMetadataBackup.js → _XoMetadataBackup.mjs} +11 -9
- package/_runners/{_createStreamThrottle.js → _createStreamThrottle.mjs} +4 -6
- package/_runners/{_forkStreamUnpipe.js → _forkStreamUnpipe.mjs} +4 -5
- package/_runners/{_getAdaptersByRemote.js → _getAdaptersByRemote.mjs} +1 -3
- package/_runners/_runTask.mjs +5 -0
- package/_runners/_vmRunners/{FullRemote.js → FullRemote.mjs} +9 -12
- package/_runners/_vmRunners/{FullXapi.js → FullXapi.mjs} +7 -9
- package/_runners/_vmRunners/{IncrementalRemote.js → IncrementalRemote.mjs} +11 -12
- package/_runners/_vmRunners/{IncrementalXapi.js → IncrementalXapi.mjs} +18 -20
- package/_runners/_vmRunners/{_Abstract.js → _Abstract.mjs} +4 -6
- package/_runners/_vmRunners/{_AbstractRemote.js → _AbstractRemote.mjs} +6 -6
- package/_runners/_vmRunners/{_AbstractXapi.js → _AbstractXapi.mjs} +14 -17
- package/_runners/_vmRunners/_forkDeltaExport.mjs +11 -0
- package/_runners/_writers/{FullRemoteWriter.js → FullRemoteWriter.mjs} +6 -8
- package/_runners/_writers/{FullXapiWriter.js → FullXapiWriter.mjs} +10 -12
- package/_runners/_writers/{IncrementalRemoteWriter.js → IncrementalRemoteWriter.mjs} +31 -28
- package/_runners/_writers/{IncrementalXapiWriter.js → IncrementalXapiWriter.mjs} +11 -13
- package/_runners/_writers/{_AbstractFullWriter.js → _AbstractFullWriter.mjs} +2 -4
- package/_runners/_writers/{_AbstractIncrementalWriter.js → _AbstractIncrementalWriter.mjs} +2 -4
- package/_runners/_writers/{_AbstractWriter.js → _AbstractWriter.mjs} +3 -5
- package/_runners/_writers/{_MixinRemoteWriter.js → _MixinRemoteWriter.mjs} +10 -12
- package/_runners/_writers/_MixinXapiWriter.mjs +72 -0
- package/_runners/_writers/_checkVhd.mjs +6 -0
- package/_runners/_writers/{_listReplicatedVms.js → _listReplicatedVms.mjs} +1 -3
- package/_runners/_writers/{_packUuid.js → _packUuid.mjs} +1 -3
- package/{_watchStreamSize.js → _watchStreamSize.mjs} +1 -3
- package/{extractIdsFromSimplePattern.js → extractIdsFromSimplePattern.mjs} +1 -3
- package/{formatVmBackups.js → formatVmBackups.mjs} +3 -5
- package/merge-worker/{cli.js → cli.mjs} +9 -11
- package/merge-worker/{index.js → index.mjs} +6 -7
- package/package.json +10 -8
- package/{parseMetadataBackupId.js → parseMetadataBackupId.mjs} +2 -4
- package/{runBackupWorker.js → runBackupWorker.mjs} +4 -7
- package/_backupType.js +0 -6
- package/_filenameDate.js +0 -8
- package/_getVmBackupDir.js +0 -8
- package/_runners/_runTask.js +0 -6
- package/_runners/_vmRunners/_forkDeltaExport.js +0 -12
- package/_runners/_writers/_MixinXapiWriter.js +0 -46
- package/_runners/_writers/_checkVhd.js +0 -8
package/{Backup.js → Backup.mjs}
RENAMED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
import { Metadata } from './_runners/Metadata.mjs'
|
|
2
|
+
import { VmsRemote } from './_runners/VmsRemote.mjs'
|
|
3
|
+
import { VmsXapi } from './_runners/VmsXapi.mjs'
|
|
2
4
|
|
|
3
|
-
|
|
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
|
-
|
|
1
|
+
import { asyncMap } from '@xen-orchestra/async-map'
|
|
2
2
|
|
|
3
|
-
|
|
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,13 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import assert from 'node:assert'
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
const
|
|
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,
|
|
51
|
-
|
|
52
|
-
async function
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
-
|
|
1
|
+
import { join, resolve } from 'node:path/posix'
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
import { DIR_XO_POOL_METADATA_BACKUPS } from './RemoteAdapter.mjs'
|
|
4
|
+
import { PATH_DB_DUMP } from './_runners/_PoolMetadataBackup.mjs'
|
|
5
5
|
|
|
6
|
-
|
|
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
|
-
|
|
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
|
}
|
package/{Task.js → Task.mjs}
RENAMED
|
@@ -1,7 +1,5 @@
|
|
|
1
|
-
|
|
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)
|
package/_backupType.mjs
ADDED
|
@@ -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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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 {
|
|
@@ -1,24 +1,22 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
|
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
|
|
18
|
-
exports.TAG_BASE_DELTA = TAG_BASE_DELTA
|
|
17
|
+
export const TAG_COPY_SRC = 'xo:copy_of'
|
|
19
18
|
|
|
20
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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',
|
package/{_lvm.js → _lvm.mjs}
RENAMED
|
@@ -1,8 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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
|
-
|
|
33
|
-
|
|
30
|
+
export const lvs = makeFunction('lvs')
|
|
31
|
+
export const pvs = makeFunction('pvs')
|