@xen-orchestra/backups 0.64.3 → 0.65.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/_runners/VmsRemote.mjs +5 -2
- package/_runners/VmsXapi.mjs +4 -1
- package/_runners/_vmRunners/FullRemote.mjs +1 -1
- package/_runners/_vmRunners/FullXapi.mjs +2 -4
- package/_runners/_vmRunners/IncrementalRemote.mjs +3 -2
- package/_runners/_vmRunners/IncrementalXapi.mjs +4 -3
- package/_runners/_vmRunners/_AbstractRemote.mjs +2 -0
- package/_runners/_vmRunners/_AbstractXapi.mjs +2 -0
- package/_runners/_writers/IncrementalXapiWriter.mjs +8 -3
- package/formatVmBackups.mjs +11 -3
- package/package.json +8 -8
package/_runners/VmsRemote.mjs
CHANGED
|
@@ -9,6 +9,7 @@ import { getAdaptersByRemote } from './_getAdaptersByRemote.mjs'
|
|
|
9
9
|
import { FullRemote } from './_vmRunners/FullRemote.mjs'
|
|
10
10
|
import { IncrementalRemote } from './_vmRunners/IncrementalRemote.mjs'
|
|
11
11
|
import { Throttle } from '@vates/generator-toolbox'
|
|
12
|
+
import createStreamThrottle from './_createStreamThrottle.mjs'
|
|
12
13
|
|
|
13
14
|
const noop = Function.prototype
|
|
14
15
|
|
|
@@ -41,7 +42,8 @@ export const VmsRemote = class RemoteVmsBackupRunner extends Abstract {
|
|
|
41
42
|
const schedule = this._schedule
|
|
42
43
|
const settings = this._settings
|
|
43
44
|
|
|
44
|
-
const throttleGenerator = new Throttle()
|
|
45
|
+
const throttleGenerator = new Throttle(settings.maxExportRate)
|
|
46
|
+
const throttleStream = createStreamThrottle(settings.maxExportRate)
|
|
45
47
|
|
|
46
48
|
const config = this._config
|
|
47
49
|
|
|
@@ -89,7 +91,8 @@ export const VmsRemote = class RemoteVmsBackupRunner extends Abstract {
|
|
|
89
91
|
schedule,
|
|
90
92
|
settings: vmSettings,
|
|
91
93
|
sourceRemoteAdapter,
|
|
92
|
-
throttleGenerator,
|
|
94
|
+
throttleGenerator, // for incrementals
|
|
95
|
+
throttleStream, // for full
|
|
93
96
|
vmUuid,
|
|
94
97
|
}
|
|
95
98
|
let vmBackup
|
package/_runners/VmsXapi.mjs
CHANGED
|
@@ -10,6 +10,7 @@ import { getAdaptersByRemote } from './_getAdaptersByRemote.mjs'
|
|
|
10
10
|
import { IncrementalXapi } from './_vmRunners/IncrementalXapi.mjs'
|
|
11
11
|
import { FullXapi } from './_vmRunners/FullXapi.mjs'
|
|
12
12
|
import { Throttle } from '@vates/generator-toolbox'
|
|
13
|
+
import createStreamThrottle from './_createStreamThrottle.mjs'
|
|
13
14
|
|
|
14
15
|
const noop = Function.prototype
|
|
15
16
|
|
|
@@ -55,7 +56,8 @@ export const VmsXapi = class VmsXapiBackupRunner extends Abstract {
|
|
|
55
56
|
const schedule = this._schedule
|
|
56
57
|
const settings = this._settings
|
|
57
58
|
|
|
58
|
-
const throttleGenerator = new Throttle()
|
|
59
|
+
const throttleGenerator = new Throttle(settings.maxExportRate)
|
|
60
|
+
const throttleStream = createStreamThrottle(settings.maxExportRate)
|
|
59
61
|
|
|
60
62
|
const config = this._config
|
|
61
63
|
|
|
@@ -148,6 +150,7 @@ export const VmsXapi = class VmsXapiBackupRunner extends Abstract {
|
|
|
148
150
|
settings: vmSettings,
|
|
149
151
|
srs,
|
|
150
152
|
throttleGenerator,
|
|
153
|
+
throttleStream,
|
|
151
154
|
vm,
|
|
152
155
|
}
|
|
153
156
|
|
|
@@ -16,7 +16,7 @@ export const FullRemote = class FullRemoteVmBackupRunner extends AbstractRemote
|
|
|
16
16
|
const transferList = await this._computeTransferList(({ mode }) => mode === 'full')
|
|
17
17
|
|
|
18
18
|
for (const metadata of transferList) {
|
|
19
|
-
const stream = await this._sourceRemoteAdapter.readFullVmBackup(metadata)
|
|
19
|
+
const stream = this._throttleStream(await this._sourceRemoteAdapter.readFullVmBackup(metadata))
|
|
20
20
|
const sizeContainer = watchStreamSize(stream)
|
|
21
21
|
|
|
22
22
|
// @todo shouldn't transfer backup if it will be deleted by retention policy (higher retention on source than destination)
|
|
@@ -49,16 +49,14 @@ export const FullXapi = class FullXapiVmBackupRunner extends AbstractXapi {
|
|
|
49
49
|
const { compression } = this.job
|
|
50
50
|
const vm = this._vm
|
|
51
51
|
const exportedVm = this._exportedVm
|
|
52
|
-
|
|
53
|
-
const stream =
|
|
54
|
-
/* this._throttleStream( */
|
|
52
|
+
const stream = this._throttleStream(
|
|
55
53
|
(
|
|
56
54
|
await this._xapi.VM_export(exportedVm.$ref, {
|
|
57
55
|
compress: Boolean(compression) && (compression === 'native' ? 'gzip' : 'zstd'),
|
|
58
56
|
useSnapshot: false,
|
|
59
57
|
})
|
|
60
58
|
).body
|
|
61
|
-
|
|
59
|
+
)
|
|
62
60
|
|
|
63
61
|
const vdis = await exportedVm.$getDisks()
|
|
64
62
|
let maxStreamLength = 1024 * 1024 // Ovf file and tar headers are a few KB, let's stay safe
|
|
@@ -10,7 +10,7 @@ import { IncrementalRemoteWriter } from '../_writers/IncrementalRemoteWriter.mjs
|
|
|
10
10
|
import { Disposable } from 'promise-toolbox'
|
|
11
11
|
import { openVhd } from 'vhd-lib'
|
|
12
12
|
import { getVmBackupDir } from '../../_getVmBackupDir.mjs'
|
|
13
|
-
import { SynchronizedDisk } from '@xen-orchestra/disk-transform'
|
|
13
|
+
import { SynchronizedDisk, ThrottledDisk } from '@xen-orchestra/disk-transform'
|
|
14
14
|
|
|
15
15
|
const { warn } = createLogger('xo:backups:Incrementalremote')
|
|
16
16
|
class IncrementalRemoteVmBackupRunner extends AbstractRemote {
|
|
@@ -71,8 +71,9 @@ class IncrementalRemoteVmBackupRunner extends AbstractRemote {
|
|
|
71
71
|
const isVhdDifferencing = {}
|
|
72
72
|
|
|
73
73
|
for (const key in incrementalExport.disks) {
|
|
74
|
-
|
|
74
|
+
let disk = incrementalExport.disks[key]
|
|
75
75
|
isVhdDifferencing[key] = disk.isDifferencing()
|
|
76
|
+
disk = new ThrottledDisk(disk, this._throttleGenerator)
|
|
76
77
|
incrementalExport.disks[key] = new SynchronizedDisk(disk)
|
|
77
78
|
}
|
|
78
79
|
|
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
setVmDeltaChainLength,
|
|
15
15
|
markExportSuccessfull,
|
|
16
16
|
} from '../../_otherConfig.mjs'
|
|
17
|
-
import { SynchronizedDisk } from '@xen-orchestra/disk-transform'
|
|
17
|
+
import { ThrottledDisk, SynchronizedDisk } from '@xen-orchestra/disk-transform'
|
|
18
18
|
|
|
19
19
|
const { debug } = createLogger('xo:backups:IncrementalXapiVmBackup')
|
|
20
20
|
|
|
@@ -43,13 +43,14 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr
|
|
|
43
43
|
const isVhdDifferencing = {}
|
|
44
44
|
let useNbd = false
|
|
45
45
|
for (const key in deltaExport.disks) {
|
|
46
|
-
|
|
46
|
+
let disk = deltaExport.disks[key]
|
|
47
47
|
isVhdDifferencing[key] = disk.isDifferencing()
|
|
48
48
|
if (!isFull && !isVhdDifferencing[key] && key !== exportedVm.$suspend_VDI?.$ref) {
|
|
49
49
|
Task.warning('Backup fell back to a full')
|
|
50
50
|
}
|
|
51
|
-
deltaExport.disks[key] = new SynchronizedDisk(disk)
|
|
52
51
|
useNbd = useNbd || disk.useNbd()
|
|
52
|
+
disk = new ThrottledDisk(disk, this._throttleGenerator)
|
|
53
|
+
deltaExport.disks[key] = new SynchronizedDisk(disk)
|
|
53
54
|
}
|
|
54
55
|
if (useNbd) {
|
|
55
56
|
Task.info('Transfer data using NBD')
|
|
@@ -24,6 +24,7 @@ export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstrac
|
|
|
24
24
|
settings,
|
|
25
25
|
sourceRemoteAdapter,
|
|
26
26
|
throttleGenerator,
|
|
27
|
+
throttleStream,
|
|
27
28
|
vmUuid,
|
|
28
29
|
}) {
|
|
29
30
|
super()
|
|
@@ -37,6 +38,7 @@ export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstrac
|
|
|
37
38
|
this._healthCheckSr = healthCheckSr
|
|
38
39
|
this._sourceRemoteAdapter = sourceRemoteAdapter
|
|
39
40
|
this._throttleGenerator = throttleGenerator
|
|
41
|
+
this._throttleStream = throttleStream
|
|
40
42
|
this._vmUuid = vmUuid
|
|
41
43
|
|
|
42
44
|
const allSettings = job.settings
|
|
@@ -29,6 +29,7 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
|
|
29
29
|
settings,
|
|
30
30
|
srs,
|
|
31
31
|
throttleGenerator,
|
|
32
|
+
throttleStream,
|
|
32
33
|
vm,
|
|
33
34
|
}) {
|
|
34
35
|
super()
|
|
@@ -63,6 +64,7 @@ export const AbstractXapi = class AbstractXapiVmBackupRunner extends Abstract {
|
|
|
63
64
|
this._jobId = job.id
|
|
64
65
|
this._jobSnapshotVdis = undefined
|
|
65
66
|
this._throttleGenerator = throttleGenerator
|
|
67
|
+
this._throttleStream = throttleStream
|
|
66
68
|
this._xapi = vm.$xapi
|
|
67
69
|
|
|
68
70
|
// Base VM for the export
|
|
@@ -10,6 +10,7 @@ import { AbstractIncrementalWriter } from './_AbstractIncrementalWriter.mjs'
|
|
|
10
10
|
import { MixinXapiWriter } from './_MixinXapiWriter.mjs'
|
|
11
11
|
import { listReplicatedVms } from './_listReplicatedVms.mjs'
|
|
12
12
|
import { COPY_OF, setVmOtherConfig, BASE_DELTA_VDI } from '../../_otherConfig.mjs'
|
|
13
|
+
import assert from 'node:assert'
|
|
13
14
|
|
|
14
15
|
export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWriter) {
|
|
15
16
|
async checkBaseVdis(baseUuidToSrcVdi) {
|
|
@@ -112,17 +113,21 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
112
113
|
|
|
113
114
|
const replicatedVdis = sr.$VDIs.filter(vdi => {
|
|
114
115
|
// REPLICATED_TO_SR_UUID is not used here since we are already filtering from sr.$VDIs
|
|
115
|
-
return sourceVdiUuids.includes(vdi?.other_config[COPY_OF])
|
|
116
|
+
return vdi?.managed && !vdi?.is_a_snapshot && sourceVdiUuids.includes(vdi?.other_config[COPY_OF])
|
|
116
117
|
})
|
|
117
118
|
|
|
118
119
|
Object.values(backup.vdis).forEach(vdi => {
|
|
119
120
|
vdi.other_config[COPY_OF] = vdi.uuid
|
|
120
121
|
if (sourceVdiUuids.length > 0) {
|
|
121
|
-
const baseReplicatedTo = replicatedVdis.
|
|
122
|
+
const baseReplicatedTo = replicatedVdis.filter(
|
|
122
123
|
replicatedVdi => replicatedVdi.other_config[COPY_OF] === vdi.other_config[BASE_DELTA_VDI]
|
|
123
124
|
)
|
|
125
|
+
assert.ok(
|
|
126
|
+
baseReplicatedTo.length <= 1,
|
|
127
|
+
`Target of a replication must be unique, got ${baseReplicatedTo.length} candidates`
|
|
128
|
+
)
|
|
124
129
|
// baseReplicatedTo can be undefined if a new disk is added and other are already replicated
|
|
125
|
-
vdi.baseVdi = baseReplicatedTo
|
|
130
|
+
vdi.baseVdi = baseReplicatedTo[0]
|
|
126
131
|
} else {
|
|
127
132
|
// first replication of this disk
|
|
128
133
|
vdi.baseVdi = undefined
|
package/formatVmBackups.mjs
CHANGED
|
@@ -18,6 +18,8 @@ function formatVmBackup(backup) {
|
|
|
18
18
|
}
|
|
19
19
|
}
|
|
20
20
|
return {
|
|
21
|
+
type: 'xo-vm-backup',
|
|
22
|
+
backupRepository: backup.backupRepositoryId,
|
|
21
23
|
disks:
|
|
22
24
|
backup.vhds === undefined
|
|
23
25
|
? []
|
|
@@ -38,6 +40,7 @@ function formatVmBackup(backup) {
|
|
|
38
40
|
size: backup.size,
|
|
39
41
|
timestamp: backup.timestamp,
|
|
40
42
|
vm: {
|
|
43
|
+
uuid: backup.vm.uuid,
|
|
41
44
|
name_description: backup.vm.name_description,
|
|
42
45
|
name_label: backup.vm.name_label,
|
|
43
46
|
},
|
|
@@ -48,7 +51,12 @@ function formatVmBackup(backup) {
|
|
|
48
51
|
}
|
|
49
52
|
}
|
|
50
53
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
+
/**
|
|
55
|
+
* format all backups as returned by RemoteAdapter#listAllVmBackups()
|
|
56
|
+
* @param {Record<string, object[]>} backupsByVM
|
|
57
|
+
* @param {string} backupRepositoryId
|
|
58
|
+
* @returns {Record<string, object[]>}
|
|
59
|
+
*/
|
|
60
|
+
export function formatVmBackups(backupsByVM, backupRepositoryId) {
|
|
61
|
+
return mapValues(backupsByVM, backups => backups.map(backup => formatVmBackup({ ...backup, backupRepositoryId })))
|
|
54
62
|
}
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
|
10
10
|
},
|
|
11
|
-
"version": "0.
|
|
11
|
+
"version": "0.65.0",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.18"
|
|
14
14
|
},
|
|
@@ -19,18 +19,18 @@
|
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@iarna/toml": "^2.2.5",
|
|
21
21
|
"@kldzj/stream-throttle": "^1.1.1",
|
|
22
|
-
"@vates/async-each": "^1.0.
|
|
22
|
+
"@vates/async-each": "^1.0.1",
|
|
23
23
|
"@vates/cached-dns.lookup": "^1.0.0",
|
|
24
24
|
"@vates/compose": "^2.1.0",
|
|
25
25
|
"@vates/decorate-with": "^2.1.0",
|
|
26
26
|
"@vates/disposable": "^0.1.6",
|
|
27
27
|
"@vates/fuse-vhd": "^2.1.2",
|
|
28
|
-
"@vates/generator-toolbox": "^1.0
|
|
28
|
+
"@vates/generator-toolbox": "^1.1.0",
|
|
29
29
|
"@vates/nbd-client": "^3.2.1",
|
|
30
30
|
"@vates/parse-duration": "^0.1.1",
|
|
31
31
|
"@xen-orchestra/async-map": "^0.1.2",
|
|
32
|
-
"@xen-orchestra/disk-transform": "^1.
|
|
33
|
-
"@xen-orchestra/fs": "^4.6.
|
|
32
|
+
"@xen-orchestra/disk-transform": "^1.2.0",
|
|
33
|
+
"@xen-orchestra/fs": "^4.6.4",
|
|
34
34
|
"@xen-orchestra/log": "^0.7.1",
|
|
35
35
|
"@xen-orchestra/qcow2": "^1.0.0",
|
|
36
36
|
"@xen-orchestra/template": "^0.1.0",
|
|
@@ -51,18 +51,18 @@
|
|
|
51
51
|
"tar": "^6.1.15",
|
|
52
52
|
"uuid": "^9.0.0",
|
|
53
53
|
"value-matcher": "^0.2.0",
|
|
54
|
-
"vhd-lib": "^4.14.
|
|
54
|
+
"vhd-lib": "^4.14.2",
|
|
55
55
|
"xen-api": "^4.7.4",
|
|
56
56
|
"yazl": "^2.5.1"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
59
|
"fs-extra": "^11.1.0",
|
|
60
|
-
"rimraf": "^
|
|
60
|
+
"rimraf": "^6.0.1",
|
|
61
61
|
"sinon": "^18.0.0",
|
|
62
62
|
"tmp": "^0.2.1"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@xen-orchestra/xapi": "^8.4.
|
|
65
|
+
"@xen-orchestra/xapi": "^8.4.2"
|
|
66
66
|
},
|
|
67
67
|
"license": "AGPL-3.0-or-later",
|
|
68
68
|
"author": {
|