@xen-orchestra/backups 0.72.1 → 0.73.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/ImportVmBackup.mjs +1 -1
- package/RemoteAdapter.mjs +8 -6
- package/_otherConfig.mjs +2 -0
- package/_runners/_vmRunners/IncrementalXapi.mjs +115 -41
- package/_runners/_vmRunners/_AbstractXapi.mjs +38 -34
- package/_runners/_writers/IncrementalXapiWriter.mjs +92 -27
- package/_runners/_writers/_MixinRemoteWriter.mjs +0 -2
- package/package.json +8 -6
- package/tests.fixtures.d.mts +47 -0
- package/_cleanVm.mjs +0 -623
- package/disks/MergeRemoteDisk.mjs +0 -324
- package/disks/RemoteDisk.mjs +0 -223
- package/disks/RemoteVhdDisk.mjs +0 -480
- package/disks/RemoteVhdDiskChain.mjs +0 -304
- package/disks/index.mjs +0 -49
- package/disks/openDiskChain.mjs +0 -40
package/disks/RemoteVhdDisk.mjs
DELETED
|
@@ -1,480 +0,0 @@
|
|
|
1
|
-
// @ts-check
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @typedef {import('vhd-lib/Vhd/VhdDirectory.js').VhdDirectory} VhdDirectory
|
|
5
|
-
* @typedef {import('vhd-lib/Vhd/VhdFile.js').VhdFile} VhdFile
|
|
6
|
-
* @typedef {import('vhd-lib/_createFooterHeader').VhdFooter} VhdFooter
|
|
7
|
-
* @typedef {import('@xen-orchestra/disk-transform').DiskBlock} DiskBlock
|
|
8
|
-
* @typedef {import('@xen-orchestra/disk-transform').FileAccessor} FileAccessor
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { openVhd, VhdAbstract, VhdDirectory } from 'vhd-lib'
|
|
14
|
-
import { RemoteDisk } from './RemoteDisk.mjs'
|
|
15
|
-
import { DISK_TYPES } from 'vhd-lib/_constants.js'
|
|
16
|
-
import { isVhdAlias, resolveVhdAlias } from 'vhd-lib/aliases.js'
|
|
17
|
-
import { stringify } from 'uuid'
|
|
18
|
-
import { dirname, join } from 'node:path'
|
|
19
|
-
import { RemoteVhdDiskChain } from './RemoteVhdDiskChain.mjs'
|
|
20
|
-
|
|
21
|
-
export class RemoteVhdDisk extends RemoteDisk {
|
|
22
|
-
/**
|
|
23
|
-
* @type {string}
|
|
24
|
-
*/
|
|
25
|
-
#path
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* @type {FileAccessor}
|
|
29
|
-
*/
|
|
30
|
-
#handler
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* @type {VhdFile | VhdDirectory | undefined}
|
|
34
|
-
*/
|
|
35
|
-
#vhd
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* @type {boolean | undefined}
|
|
39
|
-
*/
|
|
40
|
-
#isDifferencing
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @type {number}
|
|
44
|
-
*/
|
|
45
|
-
#blockSize = 2 * 1024 * 1024
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* @type {number}
|
|
49
|
-
*/
|
|
50
|
-
#bitmapSize = 512
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* @type {() => any}
|
|
54
|
-
*/
|
|
55
|
-
#dispose = () => {}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* @param {Object} params
|
|
59
|
-
* @param {FileAccessor} params.handler
|
|
60
|
-
* @param {string} params.path
|
|
61
|
-
*/
|
|
62
|
-
constructor({ handler, path }) {
|
|
63
|
-
super()
|
|
64
|
-
// @todo : ensure this is the full path from the root of the remote
|
|
65
|
-
this.#path = path
|
|
66
|
-
this.#handler = handler
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* @param {Object} [options]
|
|
71
|
-
* @param {boolean} [options.force=false]
|
|
72
|
-
* @returns {Promise<void>}
|
|
73
|
-
*/
|
|
74
|
-
async init(options = {}) {
|
|
75
|
-
if (this.#vhd === undefined) {
|
|
76
|
-
try {
|
|
77
|
-
const { value, dispose } = await openVhd(this.#handler, await resolveVhdAlias(this.#handler, this.#path), {
|
|
78
|
-
checkSecondFooter: !options.force,
|
|
79
|
-
})
|
|
80
|
-
this.#vhd = value
|
|
81
|
-
|
|
82
|
-
if ((await this.isDirectory()) && !isVhdAlias(this.#path)) {
|
|
83
|
-
this.#vhd = undefined
|
|
84
|
-
throw Object.assign(new Error("Can't init vhd directory without using alias"), { code: 'NOT_SUPPORTED' })
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
this.#dispose = dispose
|
|
88
|
-
await this.#vhd.readBlockAllocationTable()
|
|
89
|
-
this.#isDifferencing = value.footer.diskType === DISK_TYPES.DIFFERENCING
|
|
90
|
-
} catch (error) {
|
|
91
|
-
await this.close()
|
|
92
|
-
throw error
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Closes the VHD.
|
|
99
|
-
* We replace the dispose function call so the disk can only be closed once.
|
|
100
|
-
*
|
|
101
|
-
* @returns {Promise<void>}
|
|
102
|
-
*/
|
|
103
|
-
async close() {
|
|
104
|
-
const dispose = this.#dispose
|
|
105
|
-
this.#dispose = () => Promise.resolve()
|
|
106
|
-
await dispose()
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* @returns {number}
|
|
111
|
-
*/
|
|
112
|
-
getVirtualSize() {
|
|
113
|
-
if (this.#vhd === undefined) {
|
|
114
|
-
throw new Error(`can't call getVirtualSize of a RemoteVhdDisk before init`)
|
|
115
|
-
}
|
|
116
|
-
return this.#vhd.footer.currentSize
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* @returns {number} size
|
|
121
|
-
*/
|
|
122
|
-
getSizeOnDisk() {
|
|
123
|
-
if (this.#vhd === undefined) {
|
|
124
|
-
throw new Error(`can't call getVirtualSize of a RemoteVhdDisk before init`)
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return this.#vhd.streamSize()
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* @returns {number}
|
|
132
|
-
*/
|
|
133
|
-
getBlockSize() {
|
|
134
|
-
return this.#blockSize
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
/**
|
|
138
|
-
* @returns {string}
|
|
139
|
-
*/
|
|
140
|
-
getPath() {
|
|
141
|
-
return this.#path
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Returns the disk path in an array.
|
|
146
|
-
*
|
|
147
|
-
* @returns {string[]}
|
|
148
|
-
*/
|
|
149
|
-
getPaths() {
|
|
150
|
-
return [this.getPath()]
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
/**
|
|
154
|
-
* @returns {string}
|
|
155
|
-
*/
|
|
156
|
-
getUuid() {
|
|
157
|
-
if (this.#vhd === undefined) {
|
|
158
|
-
throw new Error(`can't call getUid of a RemoteVhdDisk before init`)
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
return stringify(this.#vhd.footer.uuid)
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
/**
|
|
165
|
-
* @returns {string}
|
|
166
|
-
*/
|
|
167
|
-
getParentUuid() {
|
|
168
|
-
if (this.#vhd === undefined) {
|
|
169
|
-
throw new Error(`can't call getParentUid of a RemoteVhdDisk before init`)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
return stringify(this.#vhd.header.parentUuid)
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/**
|
|
176
|
-
* @returns {Promise<boolean>} canMergeConcurently
|
|
177
|
-
*/
|
|
178
|
-
async canMergeConcurently() {
|
|
179
|
-
return await this.isDirectory()
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Checks if the VHD contains a specific block.
|
|
184
|
-
* @param {number} index
|
|
185
|
-
* @returns {boolean}
|
|
186
|
-
*/
|
|
187
|
-
hasBlock(index) {
|
|
188
|
-
if (this.#vhd === undefined) {
|
|
189
|
-
throw new Error(`can't call hasblock of a RemoteVhdDisk before init`)
|
|
190
|
-
}
|
|
191
|
-
return this.#vhd.containsBlock(index)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Gets the indexes of all blocks in the VHD.
|
|
196
|
-
* @returns {Array<number>}
|
|
197
|
-
*/
|
|
198
|
-
getBlockIndexes() {
|
|
199
|
-
if (this.#vhd === undefined) {
|
|
200
|
-
throw new Error(`can't call getBlockIndexes of a RemoteVhdDisk before init`)
|
|
201
|
-
}
|
|
202
|
-
const indexes = []
|
|
203
|
-
for (let blockIndex = 0; blockIndex < this.#vhd.header.maxTableEntries; blockIndex++) {
|
|
204
|
-
if (this.hasBlock(blockIndex)) {
|
|
205
|
-
indexes.push(blockIndex)
|
|
206
|
-
}
|
|
207
|
-
}
|
|
208
|
-
return indexes
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/**
|
|
212
|
-
* Returns the parent non inizialized instance
|
|
213
|
-
* @returns {RemoteDisk}
|
|
214
|
-
*/
|
|
215
|
-
instantiateParent() {
|
|
216
|
-
if (this.#vhd === undefined) {
|
|
217
|
-
throw new Error(`can't call instantiateParent of a RemoteVhdDisk before init`)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const parentPath = this.#vhd.header.parentUnicodeName
|
|
221
|
-
const fullParentPath = join(dirname(this.#path), parentPath)
|
|
222
|
-
|
|
223
|
-
if (!parentPath) {
|
|
224
|
-
throw new Error(`disk ${this.#path} doesn't have parents`)
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
const parent = new RemoteVhdDisk({ handler: this.#handler, path: fullParentPath })
|
|
228
|
-
return parent
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
/**
|
|
232
|
-
* Writes a full block into this VHD.
|
|
233
|
-
* @param {DiskBlock} diskBlock
|
|
234
|
-
* @return {Promise<number>} blockSize
|
|
235
|
-
*/
|
|
236
|
-
async writeBlock(diskBlock) {
|
|
237
|
-
if (this.#vhd === undefined) {
|
|
238
|
-
throw new Error(`can't call readBlock of a RemoteVhdDisk before init`)
|
|
239
|
-
}
|
|
240
|
-
await this.#vhd.writeEntireBlock({
|
|
241
|
-
id: diskBlock.index,
|
|
242
|
-
buffer: Buffer.concat([Buffer.alloc(this.#bitmapSize, 255), diskBlock.data]),
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
return this.getBlockSize()
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* Reads a specific block from the VHD.
|
|
250
|
-
* @param {number} index
|
|
251
|
-
* @returns {Promise<DiskBlock>} diskBlock
|
|
252
|
-
*/
|
|
253
|
-
async readBlock(index) {
|
|
254
|
-
if (this.#vhd === undefined) {
|
|
255
|
-
throw new Error(`can't call readBlock of a RemoteVhdDisk before init`)
|
|
256
|
-
}
|
|
257
|
-
const { data } = await this.#vhd.readBlock(index)
|
|
258
|
-
return {
|
|
259
|
-
index,
|
|
260
|
-
data,
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Reads a specific block from the child disk to copy/move it to this disk.
|
|
266
|
-
* @param {RemoteDisk} childDisk
|
|
267
|
-
* @param {number} index
|
|
268
|
-
* @param {boolean} isResumingMerge
|
|
269
|
-
* @returns {Promise<number>} blockSize
|
|
270
|
-
*/
|
|
271
|
-
async mergeBlock(childDisk, index, isResumingMerge) {
|
|
272
|
-
if (
|
|
273
|
-
(childDisk instanceof RemoteVhdDisk || childDisk instanceof RemoteVhdDiskChain) &&
|
|
274
|
-
(await this.isDirectory()) &&
|
|
275
|
-
(await childDisk.isDirectory())
|
|
276
|
-
) {
|
|
277
|
-
try {
|
|
278
|
-
await this.#handler.rename(childDisk.getBlockPath(index), this.getBlockPath(index))
|
|
279
|
-
|
|
280
|
-
this.setAllocatedBlocks([index])
|
|
281
|
-
} catch (error) {
|
|
282
|
-
// @ts-ignore
|
|
283
|
-
if (error.code === 'ENOENT' && isResumingMerge === true) {
|
|
284
|
-
// when resuming, the blocks moved since the last merge state write are
|
|
285
|
-
// not in the child anymore but it should be ok
|
|
286
|
-
|
|
287
|
-
// it will throw an error if block is missing in parent
|
|
288
|
-
// won't detect if the block was already in parent and is broken/missing in child
|
|
289
|
-
|
|
290
|
-
// since we can't know the initial size, this will create a discrepancy
|
|
291
|
-
// on the size
|
|
292
|
-
const { data } = await this.readBlock(index)
|
|
293
|
-
if (data.length !== this.getBlockSize()) {
|
|
294
|
-
throw error
|
|
295
|
-
} else {
|
|
296
|
-
this.setAllocatedBlocks([index])
|
|
297
|
-
}
|
|
298
|
-
} else {
|
|
299
|
-
throw error
|
|
300
|
-
}
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return this.getBlockSize()
|
|
304
|
-
} else {
|
|
305
|
-
return this.writeBlock(await childDisk.readBlock(index))
|
|
306
|
-
}
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
/**
|
|
310
|
-
* Gets a specific block path from the VHD directory.
|
|
311
|
-
* @param {number} index
|
|
312
|
-
* @returns {string} blockPath
|
|
313
|
-
*/
|
|
314
|
-
getBlockPath(index) {
|
|
315
|
-
if (this.#vhd === undefined) {
|
|
316
|
-
throw new Error(`can't call readBlock of a RemoteVhdDisk before init`)
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (this.#vhd instanceof VhdDirectory) {
|
|
320
|
-
return this.#vhd.getFullBlockPath(index)
|
|
321
|
-
} else {
|
|
322
|
-
throw new Error(`can't call getBlockPath of non directory VHD`)
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Manually set the disk allocated blocks.
|
|
328
|
-
* @param {Array<number>} blockIds
|
|
329
|
-
* @returns {Promise<void>}
|
|
330
|
-
*/
|
|
331
|
-
async setAllocatedBlocks(blockIds) {
|
|
332
|
-
if (this.#vhd instanceof VhdDirectory) {
|
|
333
|
-
this.#vhd.setAllocatedBlocks(blockIds)
|
|
334
|
-
}
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
/**
|
|
338
|
-
* Ensure that the disk can handle at least the new block count.
|
|
339
|
-
* @param {number} blockCount
|
|
340
|
-
* @returns {Promise<void>}
|
|
341
|
-
*/
|
|
342
|
-
async resize(blockCount) {
|
|
343
|
-
if (this.#vhd === undefined) {
|
|
344
|
-
throw new Error(`can't call resize of a RemoteVhdDisk before init`)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Checks that the BAT is at least as big as the provided block count, if not, increases it and shift the blocks position
|
|
348
|
-
await this.#vhd.ensureBatSize(blockCount)
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* Writes Block Allocation Table
|
|
353
|
-
* @param {RemoteDisk} childDisk
|
|
354
|
-
* @returns {Promise<void>}
|
|
355
|
-
*/
|
|
356
|
-
async flushMetadata(childDisk) {
|
|
357
|
-
if (this.#vhd === undefined) {
|
|
358
|
-
throw new Error(`can't call flushMetadata of a RemoteVhdDisk before init`)
|
|
359
|
-
}
|
|
360
|
-
|
|
361
|
-
await this.#vhd.writeBlockAllocationTable()
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
/**
|
|
365
|
-
* @returns {VhdFooter}
|
|
366
|
-
*/
|
|
367
|
-
getMetadata() {
|
|
368
|
-
if (this.#vhd === undefined) {
|
|
369
|
-
throw new Error(`can't call getMetadata of a RemoteVhdDisk before init`)
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
return this.#vhd.footer
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
/**
|
|
376
|
-
* @param {RemoteVhdDisk} childDisk
|
|
377
|
-
* @returns {Promise<void>}
|
|
378
|
-
*/
|
|
379
|
-
async mergeMetadata(childDisk) {
|
|
380
|
-
const childDiskMetadata = childDisk.getMetadata()
|
|
381
|
-
|
|
382
|
-
if (this.#vhd === undefined) {
|
|
383
|
-
throw new Error(`can't call mergeMetadata of a RemoteVhdDisk before init`)
|
|
384
|
-
}
|
|
385
|
-
|
|
386
|
-
// @ts-ignore
|
|
387
|
-
this.#vhd.footer.currentSize = childDiskMetadata.currentSize
|
|
388
|
-
// @ts-ignore
|
|
389
|
-
this.#vhd.footer.diskGeometry = { ...childDiskMetadata.diskGeometry }
|
|
390
|
-
// @ts-ignore
|
|
391
|
-
this.#vhd.footer.originalSize = childDiskMetadata.originalSize
|
|
392
|
-
// @ts-ignore
|
|
393
|
-
this.#vhd.footer.timestamp = childDiskMetadata.timestamp
|
|
394
|
-
// @ts-ignore
|
|
395
|
-
this.#vhd.footer.uuid = childDiskMetadata.uuid
|
|
396
|
-
|
|
397
|
-
await this.#vhd.writeFooter()
|
|
398
|
-
}
|
|
399
|
-
|
|
400
|
-
/**
|
|
401
|
-
* Checks if the VHD is a differencing disk.
|
|
402
|
-
* @returns {boolean}
|
|
403
|
-
*/
|
|
404
|
-
isDifferencing() {
|
|
405
|
-
if (this.#isDifferencing === undefined) {
|
|
406
|
-
throw new Error(`can't call isDifferencing of a RemoteVhdDisk before init`)
|
|
407
|
-
}
|
|
408
|
-
return this.#isDifferencing
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
/**
|
|
412
|
-
* Rename alias/disk
|
|
413
|
-
* @param {string} newPath
|
|
414
|
-
*/
|
|
415
|
-
async rename(newPath) {
|
|
416
|
-
if (isVhdAlias(newPath)) {
|
|
417
|
-
const dataPath = await resolveVhdAlias(this.#handler, this.#path)
|
|
418
|
-
|
|
419
|
-
await this.#handler.unlink(this.#path)
|
|
420
|
-
await this.#handler.unlink(newPath)
|
|
421
|
-
|
|
422
|
-
await VhdAbstract.createAlias(this.#handler, newPath, dataPath)
|
|
423
|
-
|
|
424
|
-
this.#path = newPath
|
|
425
|
-
} else {
|
|
426
|
-
try {
|
|
427
|
-
await this.#handler.unlink(newPath)
|
|
428
|
-
} catch (err) {
|
|
429
|
-
if (err && typeof err === 'object' && 'code' in err && err.code === 'EISDIR') {
|
|
430
|
-
await this.#handler.rmtree(newPath).catch(() => {})
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
await this.#handler.rename(this.#path, newPath)
|
|
435
|
-
|
|
436
|
-
this.#path = newPath
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
|
|
440
|
-
/**
|
|
441
|
-
* Deletes disk
|
|
442
|
-
*/
|
|
443
|
-
async unlink() {
|
|
444
|
-
if (this.#vhd === undefined) {
|
|
445
|
-
throw new Error(`can't call unlink of a RemoteVhdDisk before init`)
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
await this.close()
|
|
449
|
-
|
|
450
|
-
if (isVhdAlias(this.#path)) {
|
|
451
|
-
try {
|
|
452
|
-
await this.#handler.unlink(await resolveVhdAlias(this.#handler, this.#path))
|
|
453
|
-
} catch (err) {
|
|
454
|
-
if (err && typeof err === 'object' && 'code' in err && err.code === 'EISDIR') {
|
|
455
|
-
await this.#handler.rmtree(await resolveVhdAlias(this.#handler, this.#path)).catch(() => {})
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
try {
|
|
461
|
-
await this.#handler.unlink(this.#path)
|
|
462
|
-
} catch (err) {
|
|
463
|
-
if (err && typeof err === 'object' && 'code' in err && err.code === 'EISDIR') {
|
|
464
|
-
await this.#handler.rmtree(this.#path).catch(() => {})
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Check if the disk is a VHD directory.
|
|
471
|
-
* @returns {Promise<boolean>}
|
|
472
|
-
*/
|
|
473
|
-
async isDirectory() {
|
|
474
|
-
if (this.#vhd === undefined) {
|
|
475
|
-
throw new Error(`can't call isDirectory of a RemoteVhdDisk before init`)
|
|
476
|
-
}
|
|
477
|
-
|
|
478
|
-
return this.#vhd instanceof VhdDirectory
|
|
479
|
-
}
|
|
480
|
-
}
|