@xen-orchestra/backups 0.71.1 → 0.71.3
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/_incrementalVm.mjs +10 -6
- package/_runners/_vmRunners/_AbstractRemote.mjs +2 -2
- package/_runners/_writers/IncrementalXapiWriter.mjs +12 -2
- package/_runners/_writers/_AbstractAggregatedRemoteWriter.mjs +1 -1
- package/disks/MergeRemoteDisk.mjs +6 -0
- package/disks/RemoteDisk.mjs +10 -0
- package/disks/RemoteVhdDisk.mjs +15 -1
- package/disks/RemoteVhdDiskChain.mjs +38 -6
- package/package.json +3 -2
package/_incrementalVm.mjs
CHANGED
|
@@ -365,12 +365,16 @@ export const importIncrementalVm = defer(async function importIncrementalVm(
|
|
|
365
365
|
}
|
|
366
366
|
}),
|
|
367
367
|
])
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
368
|
+
|
|
369
|
+
// recreate vtpm (note there is normally only one VTPM per VM at most)
|
|
370
|
+
const existingVtpmRefs = await xapi.getField('VM', vmRef, 'VTPMs')
|
|
371
|
+
for (const vtpmRef of existingVtpmRefs ?? []) {
|
|
372
|
+
await xapi.call('VTPM_destroy', vtpmRef)
|
|
373
|
+
}
|
|
374
|
+
for (const contents of incrementalVm.vtpms ?? []) {
|
|
375
|
+
await xapi.VTPM_create({ VM: vmRef, contents })
|
|
376
|
+
}
|
|
377
|
+
|
|
374
378
|
const vm = await xapi.getRecord('VM', vmRef)
|
|
375
379
|
await Promise.all([
|
|
376
380
|
vmRecord.ha_always_run && xapi.setField('VM', vmRef, 'ha_always_run', true),
|
|
@@ -54,7 +54,7 @@ export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstrac
|
|
|
54
54
|
config,
|
|
55
55
|
healthCheckSr,
|
|
56
56
|
job,
|
|
57
|
-
|
|
57
|
+
schedule,
|
|
58
58
|
vmUuid,
|
|
59
59
|
settings,
|
|
60
60
|
})
|
|
@@ -72,7 +72,7 @@ export const AbstractRemote = class AbstractRemoteVmBackupRunner extends Abstrac
|
|
|
72
72
|
config,
|
|
73
73
|
healthCheckSr,
|
|
74
74
|
job,
|
|
75
|
-
|
|
75
|
+
schedule,
|
|
76
76
|
vmUuid,
|
|
77
77
|
remoteId,
|
|
78
78
|
settings: targetSettings,
|
|
@@ -65,8 +65,9 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
65
65
|
snapshotCandidates,
|
|
66
66
|
async snapshot => {
|
|
67
67
|
let diffDisk
|
|
68
|
+
let activeVdi
|
|
68
69
|
try {
|
|
69
|
-
|
|
70
|
+
activeVdi = sr.$xapi.getObject(snapshot.$snapshot_of)
|
|
70
71
|
const userVbds = activeVdi.$VBDs?.filter(vbd => vbd.$VM && !vbd.$VM.is_control_domain) ?? []
|
|
71
72
|
if (userVbds.length !== 1) {
|
|
72
73
|
debug('checkBaseVdis, share vbd ', { ref: snapshot.$ref, userVbds })
|
|
@@ -81,7 +82,12 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
81
82
|
// but it indicates the users played with the blocked operations
|
|
82
83
|
return
|
|
83
84
|
}
|
|
84
|
-
diffDisk = new XapiDiskSource({
|
|
85
|
+
diffDisk = new XapiDiskSource({
|
|
86
|
+
xapi: sr.$xapi,
|
|
87
|
+
vdiRef: activeVdi.$ref,
|
|
88
|
+
baseRef: snapshot.$ref,
|
|
89
|
+
onlyListChangedBlocks: true,
|
|
90
|
+
})
|
|
85
91
|
await diffDisk.init()
|
|
86
92
|
if (diffDisk.getBlockIndexes().length === 0) {
|
|
87
93
|
const sourceUuid = snapshot.other_config?.[COPY_OF]
|
|
@@ -103,6 +109,10 @@ export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWr
|
|
|
103
109
|
return
|
|
104
110
|
} finally {
|
|
105
111
|
await diffDisk?.close().catch(error => debug('checkBaseVdis, error closing', error))
|
|
112
|
+
await sr.$xapi.VDI_disconnectFromControlDomain(snapshot.$ref)
|
|
113
|
+
if (activeVdi !== undefined) {
|
|
114
|
+
await sr.$xapi.VDI_disconnectFromControlDomain(activeVdi.$ref)
|
|
115
|
+
}
|
|
106
116
|
}
|
|
107
117
|
},
|
|
108
118
|
{
|
|
@@ -107,7 +107,7 @@ export class AbstractAggregatedRemoteWriter {
|
|
|
107
107
|
}
|
|
108
108
|
|
|
109
109
|
async getEntriesPerAdapter(adapter) {
|
|
110
|
-
const scheduleId = this.#props.
|
|
110
|
+
const scheduleId = this.#props.schedule.id
|
|
111
111
|
const vmUuid = this.#props.vmUuid
|
|
112
112
|
return (await adapter.listVmBackups(vmUuid, _ => _.scheduleId === scheduleId)).map(entry => ({
|
|
113
113
|
...entry,
|
|
@@ -10,6 +10,7 @@ import { createLogger } from '@xen-orchestra/log'
|
|
|
10
10
|
|
|
11
11
|
import { basename, dirname } from 'path'
|
|
12
12
|
import { asyncEach } from '@vates/async-each'
|
|
13
|
+
import { relativeFromFile } from '@xen-orchestra/fs/path'
|
|
13
14
|
|
|
14
15
|
// @ts-ignore
|
|
15
16
|
const { warn } = createLogger('remote-disk:merge')
|
|
@@ -18,6 +19,7 @@ const { warn } = createLogger('remote-disk:merge')
|
|
|
18
19
|
* @typedef {Object} MergeState
|
|
19
20
|
* @property {{ uuid: string }} child
|
|
20
21
|
* @property {{ uuid: string }} parent
|
|
22
|
+
* @property { string[] | undefined} chain
|
|
21
23
|
* @property {number} currentBlock
|
|
22
24
|
* @property {number} mergedDataSize
|
|
23
25
|
* @property {'mergeBlocks' | 'cleanup'} step
|
|
@@ -32,6 +34,7 @@ export class MergeRemoteDisk {
|
|
|
32
34
|
#state = {
|
|
33
35
|
child: { uuid: '0' },
|
|
34
36
|
parent: { uuid: '0' },
|
|
37
|
+
chain: undefined,
|
|
35
38
|
currentBlock: 0,
|
|
36
39
|
mergedDataSize: 0,
|
|
37
40
|
step: 'mergeBlocks',
|
|
@@ -205,6 +208,9 @@ export class MergeRemoteDisk {
|
|
|
205
208
|
} else {
|
|
206
209
|
this.#state.child = { uuid: childDisk.getUuid() ?? undefined }
|
|
207
210
|
this.#state.parent = { uuid: parentDisk.getUuid() ?? undefined }
|
|
211
|
+
this.#state.chain = [parentDisk.getPath(), ...childDisk.getPaths()].map(path =>
|
|
212
|
+
relativeFromFile(this.#statePath, path)
|
|
213
|
+
)
|
|
208
214
|
|
|
209
215
|
// Finds first allocated block for the 2 following loops
|
|
210
216
|
while (this.#state.currentBlock < getMaxBlockCount && !childDisk.hasBlock(this.#state.currentBlock)) {
|
package/disks/RemoteDisk.mjs
CHANGED
|
@@ -55,6 +55,16 @@ export class RemoteDisk extends RandomAccessDisk {
|
|
|
55
55
|
throw new Error(`getPath must be implemented`)
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Abstract
|
|
60
|
+
* Returns an array of disk paths.
|
|
61
|
+
*
|
|
62
|
+
* @returns {string[]}
|
|
63
|
+
*/
|
|
64
|
+
getPaths() {
|
|
65
|
+
throw new Error(`getPaths must be implemented`)
|
|
66
|
+
}
|
|
67
|
+
|
|
58
68
|
/**
|
|
59
69
|
* Abstract
|
|
60
70
|
* @returns {string}
|
package/disks/RemoteVhdDisk.mjs
CHANGED
|
@@ -16,6 +16,7 @@ import { DISK_TYPES } from 'vhd-lib/_constants.js'
|
|
|
16
16
|
import { isVhdAlias, resolveVhdAlias } from 'vhd-lib/aliases.js'
|
|
17
17
|
import { stringify } from 'uuid'
|
|
18
18
|
import { dirname, join } from 'node:path'
|
|
19
|
+
import { RemoteVhdDiskChain } from './RemoteVhdDiskChain.mjs'
|
|
19
20
|
|
|
20
21
|
export class RemoteVhdDisk extends RemoteDisk {
|
|
21
22
|
/**
|
|
@@ -140,6 +141,15 @@ export class RemoteVhdDisk extends RemoteDisk {
|
|
|
140
141
|
return this.#path
|
|
141
142
|
}
|
|
142
143
|
|
|
144
|
+
/**
|
|
145
|
+
* Returns the disk path in an array.
|
|
146
|
+
*
|
|
147
|
+
* @returns {string[]}
|
|
148
|
+
*/
|
|
149
|
+
getPaths() {
|
|
150
|
+
return [this.getPath()]
|
|
151
|
+
}
|
|
152
|
+
|
|
143
153
|
/**
|
|
144
154
|
* @returns {string}
|
|
145
155
|
*/
|
|
@@ -259,7 +269,11 @@ export class RemoteVhdDisk extends RemoteDisk {
|
|
|
259
269
|
* @returns {Promise<number>} blockSize
|
|
260
270
|
*/
|
|
261
271
|
async mergeBlock(childDisk, index, isResumingMerge) {
|
|
262
|
-
if (
|
|
272
|
+
if (
|
|
273
|
+
(childDisk instanceof RemoteVhdDisk || childDisk instanceof RemoteVhdDiskChain) &&
|
|
274
|
+
(await this.isDirectory()) &&
|
|
275
|
+
(await childDisk.isDirectory())
|
|
276
|
+
) {
|
|
263
277
|
try {
|
|
264
278
|
await this.#handler.rename(childDisk.getBlockPath(index), this.getBlockPath(index))
|
|
265
279
|
|
|
@@ -123,6 +123,15 @@ export class RemoteVhdDiskChain extends RemoteDisk {
|
|
|
123
123
|
return this.#disks[this.#disks.length - 1].getPath()
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Disk chains return an array of disk paths.
|
|
128
|
+
*
|
|
129
|
+
* @returns {string[]}
|
|
130
|
+
*/
|
|
131
|
+
getPaths() {
|
|
132
|
+
return this.#disks.map(disk => disk.getPath())
|
|
133
|
+
}
|
|
134
|
+
|
|
126
135
|
/**
|
|
127
136
|
* @returns {string}
|
|
128
137
|
*/
|
|
@@ -134,12 +143,7 @@ export class RemoteVhdDiskChain extends RemoteDisk {
|
|
|
134
143
|
* @returns {Promise<boolean>} canMergeConcurently
|
|
135
144
|
*/
|
|
136
145
|
async canMergeConcurently() {
|
|
137
|
-
|
|
138
|
-
if (!(await disk.isDirectory())) {
|
|
139
|
-
return true
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
return false
|
|
146
|
+
return this.isDirectory()
|
|
143
147
|
}
|
|
144
148
|
|
|
145
149
|
/**
|
|
@@ -221,6 +225,21 @@ export class RemoteVhdDiskChain extends RemoteDisk {
|
|
|
221
225
|
throw new Error(`Can't merge block into a disk chain`)
|
|
222
226
|
}
|
|
223
227
|
|
|
228
|
+
/**
|
|
229
|
+
* Gets a specific block path from the VHD directory disk.
|
|
230
|
+
* @param {number} index
|
|
231
|
+
* @returns {string} blockPath
|
|
232
|
+
*/
|
|
233
|
+
getBlockPath(index) {
|
|
234
|
+
for (const disk of [...this.#disks].reverse()) {
|
|
235
|
+
if (disk.hasBlock(index)) {
|
|
236
|
+
return disk.getBlockPath(index)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
throw new Error(`Block ${index} not found in chain`)
|
|
241
|
+
}
|
|
242
|
+
|
|
224
243
|
/**
|
|
225
244
|
* @returns {VhdFooter}
|
|
226
245
|
*/
|
|
@@ -269,4 +288,17 @@ export class RemoteVhdDiskChain extends RemoteDisk {
|
|
|
269
288
|
await disk.unlink()
|
|
270
289
|
}
|
|
271
290
|
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Check if all the disks in the chain are VHD directories.
|
|
294
|
+
* @returns {Promise<boolean>}
|
|
295
|
+
*/
|
|
296
|
+
async isDirectory() {
|
|
297
|
+
for (const disk of this.#disks) {
|
|
298
|
+
if (!(await disk.isDirectory())) {
|
|
299
|
+
return false
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return true
|
|
303
|
+
}
|
|
272
304
|
}
|
package/package.json
CHANGED
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
"type": "git",
|
|
9
9
|
"url": "https://github.com/vatesfr/xen-orchestra.git"
|
|
10
10
|
},
|
|
11
|
-
"version": "0.71.
|
|
11
|
+
"version": "0.71.3",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.18"
|
|
14
14
|
},
|
|
15
15
|
"scripts": {
|
|
16
16
|
"postversion": "npm publish --access public",
|
|
17
|
+
"test": "node --test",
|
|
17
18
|
"test-integration": "node --test *.integ.mjs"
|
|
18
19
|
},
|
|
19
20
|
"dependencies": {
|
|
@@ -62,7 +63,7 @@
|
|
|
62
63
|
"tmp": "^0.2.1"
|
|
63
64
|
},
|
|
64
65
|
"peerDependencies": {
|
|
65
|
-
"@xen-orchestra/xapi": "^8.7.
|
|
66
|
+
"@xen-orchestra/xapi": "^8.7.1"
|
|
66
67
|
},
|
|
67
68
|
"license": "AGPL-3.0-or-later",
|
|
68
69
|
"author": {
|