@xen-orchestra/backups 0.29.1 → 0.29.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/RemoteAdapter.js +26 -24
- package/_cleanVm.js +45 -19
- package/package.json +2 -2
package/RemoteAdapter.js
CHANGED
|
@@ -232,21 +232,23 @@ class RemoteAdapter {
|
|
|
232
232
|
return promise
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
-
#removeVmBackupsFromCache(backups) {
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
235
|
+
async #removeVmBackupsFromCache(backups) {
|
|
236
|
+
await asyncEach(
|
|
237
|
+
Object.entries(
|
|
238
|
+
groupBy(
|
|
239
|
+
backups.map(_ => _._filename),
|
|
240
|
+
dirname
|
|
241
|
+
)
|
|
242
|
+
),
|
|
243
|
+
([dir, filenames]) =>
|
|
244
|
+
// will not reject
|
|
245
|
+
this._updateCache(dir + '/cache.json.gz', backups => {
|
|
246
|
+
for (const filename of filenames) {
|
|
247
|
+
debug('removing cache entry', { entry: filename })
|
|
248
|
+
delete backups[filename]
|
|
249
|
+
}
|
|
250
|
+
})
|
|
251
|
+
)
|
|
250
252
|
}
|
|
251
253
|
|
|
252
254
|
async deleteDeltaVmBackups(backups) {
|
|
@@ -255,7 +257,7 @@ class RemoteAdapter {
|
|
|
255
257
|
// this will delete the json, unused VHDs will be detected by `cleanVm`
|
|
256
258
|
await asyncMapSettled(backups, ({ _filename }) => handler.unlink(_filename))
|
|
257
259
|
|
|
258
|
-
this.#removeVmBackupsFromCache(backups)
|
|
260
|
+
await this.#removeVmBackupsFromCache(backups)
|
|
259
261
|
}
|
|
260
262
|
|
|
261
263
|
async deleteMetadataBackup(backupId) {
|
|
@@ -284,7 +286,7 @@ class RemoteAdapter {
|
|
|
284
286
|
Promise.all([handler.unlink(_filename), handler.unlink(resolveRelativeFromFile(_filename, xva))])
|
|
285
287
|
)
|
|
286
288
|
|
|
287
|
-
this.#removeVmBackupsFromCache(backups)
|
|
289
|
+
await this.#removeVmBackupsFromCache(backups)
|
|
288
290
|
}
|
|
289
291
|
|
|
290
292
|
deleteVmBackup(file) {
|
|
@@ -508,7 +510,7 @@ class RemoteAdapter {
|
|
|
508
510
|
return `${BACKUP_DIR}/${vmUuid}/cache.json.gz`
|
|
509
511
|
}
|
|
510
512
|
|
|
511
|
-
async
|
|
513
|
+
async _readCache(path) {
|
|
512
514
|
try {
|
|
513
515
|
return JSON.parse(await fromCallback(zlib.gunzip, await this.handler.readFile(path)))
|
|
514
516
|
} catch (error) {
|
|
@@ -521,15 +523,15 @@ class RemoteAdapter {
|
|
|
521
523
|
_updateCache = synchronized.withKey()(this._updateCache)
|
|
522
524
|
// eslint-disable-next-line no-dupe-class-members
|
|
523
525
|
async _updateCache(path, fn) {
|
|
524
|
-
const cache = await this
|
|
526
|
+
const cache = await this._readCache(path)
|
|
525
527
|
if (cache !== undefined) {
|
|
526
528
|
fn(cache)
|
|
527
529
|
|
|
528
|
-
await this
|
|
530
|
+
await this._writeCache(path, cache)
|
|
529
531
|
}
|
|
530
532
|
}
|
|
531
533
|
|
|
532
|
-
async
|
|
534
|
+
async _writeCache(path, data) {
|
|
533
535
|
try {
|
|
534
536
|
await this.handler.writeFile(path, await fromCallback(zlib.gzip, JSON.stringify(data)), { flags: 'w' })
|
|
535
537
|
} catch (error) {
|
|
@@ -577,7 +579,7 @@ class RemoteAdapter {
|
|
|
577
579
|
async _readCacheListVmBackups(vmUuid) {
|
|
578
580
|
const path = this.#getVmBackupsCache(vmUuid)
|
|
579
581
|
|
|
580
|
-
const cache = await this
|
|
582
|
+
const cache = await this._readCache(path)
|
|
581
583
|
if (cache !== undefined) {
|
|
582
584
|
debug('found VM backups cache, using it', { path })
|
|
583
585
|
return cache
|
|
@@ -590,7 +592,7 @@ class RemoteAdapter {
|
|
|
590
592
|
}
|
|
591
593
|
|
|
592
594
|
// detached async action, will not reject
|
|
593
|
-
this
|
|
595
|
+
this._writeCache(path, backups)
|
|
594
596
|
|
|
595
597
|
return backups
|
|
596
598
|
}
|
|
@@ -641,7 +643,7 @@ class RemoteAdapter {
|
|
|
641
643
|
})
|
|
642
644
|
|
|
643
645
|
// will not throw
|
|
644
|
-
this._updateCache(this.#getVmBackupsCache(vmUuid), backups => {
|
|
646
|
+
await this._updateCache(this.#getVmBackupsCache(vmUuid), backups => {
|
|
645
647
|
debug('adding cache entry', { entry: path })
|
|
646
648
|
backups[path] = {
|
|
647
649
|
...metadata,
|
package/_cleanVm.js
CHANGED
|
@@ -311,7 +311,6 @@ exports.cleanVm = async function cleanVm(
|
|
|
311
311
|
}
|
|
312
312
|
|
|
313
313
|
const jsons = new Set()
|
|
314
|
-
let mustInvalidateCache = false
|
|
315
314
|
const xvas = new Set()
|
|
316
315
|
const xvaSums = []
|
|
317
316
|
const entries = await handler.list(vmDir, {
|
|
@@ -327,6 +326,20 @@ exports.cleanVm = async function cleanVm(
|
|
|
327
326
|
}
|
|
328
327
|
})
|
|
329
328
|
|
|
329
|
+
const cachePath = vmDir + '/cache.json.gz'
|
|
330
|
+
|
|
331
|
+
let mustRegenerateCache
|
|
332
|
+
{
|
|
333
|
+
const cache = await this._readCache(cachePath)
|
|
334
|
+
const actual = cache === undefined ? 0 : Object.keys(cache).length
|
|
335
|
+
const expected = jsons.size
|
|
336
|
+
|
|
337
|
+
mustRegenerateCache = actual !== expected
|
|
338
|
+
if (mustRegenerateCache) {
|
|
339
|
+
logWarn('unexpected number of entries in backup cache', { path: cachePath, actual, expected })
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
330
343
|
await asyncMap(xvas, async path => {
|
|
331
344
|
// check is not good enough to delete the file, the best we can do is report
|
|
332
345
|
// it
|
|
@@ -338,6 +351,8 @@ exports.cleanVm = async function cleanVm(
|
|
|
338
351
|
const unusedVhds = new Set(vhds)
|
|
339
352
|
const unusedXvas = new Set(xvas)
|
|
340
353
|
|
|
354
|
+
const backups = new Map()
|
|
355
|
+
|
|
341
356
|
// compile the list of unused XVAs and VHDs, and remove backup metadata which
|
|
342
357
|
// reference a missing XVA/VHD
|
|
343
358
|
await asyncMap(jsons, async json => {
|
|
@@ -350,19 +365,16 @@ exports.cleanVm = async function cleanVm(
|
|
|
350
365
|
return
|
|
351
366
|
}
|
|
352
367
|
|
|
368
|
+
let isBackupComplete
|
|
369
|
+
|
|
353
370
|
const { mode } = metadata
|
|
354
371
|
if (mode === 'full') {
|
|
355
372
|
const linkedXva = resolve('/', vmDir, metadata.xva)
|
|
356
|
-
|
|
373
|
+
isBackupComplete = xvas.has(linkedXva)
|
|
374
|
+
if (isBackupComplete) {
|
|
357
375
|
unusedXvas.delete(linkedXva)
|
|
358
376
|
} else {
|
|
359
377
|
logWarn('the XVA linked to the backup is missing', { backup: json, xva: linkedXva })
|
|
360
|
-
if (remove) {
|
|
361
|
-
logInfo('deleting incomplete backup', { path: json })
|
|
362
|
-
jsons.delete(json)
|
|
363
|
-
mustInvalidateCache = true
|
|
364
|
-
await handler.unlink(json)
|
|
365
|
-
}
|
|
366
378
|
}
|
|
367
379
|
} else if (mode === 'delta') {
|
|
368
380
|
const linkedVhds = (() => {
|
|
@@ -371,22 +383,28 @@ exports.cleanVm = async function cleanVm(
|
|
|
371
383
|
})()
|
|
372
384
|
|
|
373
385
|
const missingVhds = linkedVhds.filter(_ => !vhds.has(_))
|
|
386
|
+
isBackupComplete = missingVhds.length === 0
|
|
374
387
|
|
|
375
388
|
// FIXME: find better approach by keeping as much of the backup as
|
|
376
389
|
// possible (existing disks) even if one disk is missing
|
|
377
|
-
if (
|
|
390
|
+
if (isBackupComplete) {
|
|
378
391
|
linkedVhds.forEach(_ => unusedVhds.delete(_))
|
|
379
392
|
linkedVhds.forEach(path => {
|
|
380
393
|
vhdsToJSons[path] = json
|
|
381
394
|
})
|
|
382
395
|
} else {
|
|
383
396
|
logWarn('some VHDs linked to the backup are missing', { backup: json, missingVhds })
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
if (isBackupComplete) {
|
|
401
|
+
backups.set(json, metadata)
|
|
402
|
+
} else {
|
|
403
|
+
jsons.delete(json)
|
|
404
|
+
if (remove) {
|
|
405
|
+
logInfo('deleting incomplete backup', { backup: json })
|
|
406
|
+
mustRegenerateCache = true
|
|
407
|
+
await handler.unlink(json)
|
|
390
408
|
}
|
|
391
409
|
}
|
|
392
410
|
})
|
|
@@ -496,7 +514,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
496
514
|
// check for the other that the size is the same as the real file size
|
|
497
515
|
|
|
498
516
|
await asyncMap(jsons, async metadataPath => {
|
|
499
|
-
const metadata =
|
|
517
|
+
const metadata = backups.get(metadataPath)
|
|
500
518
|
|
|
501
519
|
let fileSystemSize
|
|
502
520
|
const merged = metadataWithMergedVhd[metadataPath] !== undefined
|
|
@@ -538,6 +556,7 @@ exports.cleanVm = async function cleanVm(
|
|
|
538
556
|
// systematically update size after a merge
|
|
539
557
|
if ((merged || fixMetadata) && size !== fileSystemSize) {
|
|
540
558
|
metadata.size = fileSystemSize
|
|
559
|
+
mustRegenerateCache = true
|
|
541
560
|
try {
|
|
542
561
|
await handler.writeFile(metadataPath, JSON.stringify(metadata), { flags: 'w' })
|
|
543
562
|
} catch (error) {
|
|
@@ -546,9 +565,16 @@ exports.cleanVm = async function cleanVm(
|
|
|
546
565
|
}
|
|
547
566
|
})
|
|
548
567
|
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
568
|
+
if (mustRegenerateCache) {
|
|
569
|
+
const cache = {}
|
|
570
|
+
for (const [path, content] of backups.entries()) {
|
|
571
|
+
cache[path] = {
|
|
572
|
+
_filename: path,
|
|
573
|
+
id: path,
|
|
574
|
+
...content,
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
await this._writeCache(cachePath, cache)
|
|
552
578
|
}
|
|
553
579
|
|
|
554
580
|
return {
|
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.29.
|
|
11
|
+
"version": "0.29.3",
|
|
12
12
|
"engines": {
|
|
13
13
|
"node": ">=14.6"
|
|
14
14
|
},
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"tmp": "^0.2.1"
|
|
53
53
|
},
|
|
54
54
|
"peerDependencies": {
|
|
55
|
-
"@xen-orchestra/xapi": "^1.5.
|
|
55
|
+
"@xen-orchestra/xapi": "^1.5.3"
|
|
56
56
|
},
|
|
57
57
|
"license": "AGPL-3.0-or-later",
|
|
58
58
|
"author": {
|