@xen-orchestra/backups 0.29.1 → 0.29.2

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 CHANGED
@@ -508,7 +508,7 @@ class RemoteAdapter {
508
508
  return `${BACKUP_DIR}/${vmUuid}/cache.json.gz`
509
509
  }
510
510
 
511
- async #readCache(path) {
511
+ async _readCache(path) {
512
512
  try {
513
513
  return JSON.parse(await fromCallback(zlib.gunzip, await this.handler.readFile(path)))
514
514
  } catch (error) {
@@ -521,15 +521,15 @@ class RemoteAdapter {
521
521
  _updateCache = synchronized.withKey()(this._updateCache)
522
522
  // eslint-disable-next-line no-dupe-class-members
523
523
  async _updateCache(path, fn) {
524
- const cache = await this.#readCache(path)
524
+ const cache = await this._readCache(path)
525
525
  if (cache !== undefined) {
526
526
  fn(cache)
527
527
 
528
- await this.#writeCache(path, cache)
528
+ await this._writeCache(path, cache)
529
529
  }
530
530
  }
531
531
 
532
- async #writeCache(path, data) {
532
+ async _writeCache(path, data) {
533
533
  try {
534
534
  await this.handler.writeFile(path, await fromCallback(zlib.gzip, JSON.stringify(data)), { flags: 'w' })
535
535
  } catch (error) {
@@ -577,7 +577,7 @@ class RemoteAdapter {
577
577
  async _readCacheListVmBackups(vmUuid) {
578
578
  const path = this.#getVmBackupsCache(vmUuid)
579
579
 
580
- const cache = await this.#readCache(path)
580
+ const cache = await this._readCache(path)
581
581
  if (cache !== undefined) {
582
582
  debug('found VM backups cache, using it', { path })
583
583
  return cache
@@ -590,7 +590,7 @@ class RemoteAdapter {
590
590
  }
591
591
 
592
592
  // detached async action, will not reject
593
- this.#writeCache(path, backups)
593
+ this._writeCache(path, backups)
594
594
 
595
595
  return backups
596
596
  }
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
- if (xvas.has(linkedXva)) {
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 (missingVhds.length === 0) {
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
- if (remove) {
385
- logInfo('deleting incomplete backup', { path: json })
386
- mustInvalidateCache = true
387
- jsons.delete(json)
388
- await handler.unlink(json)
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 = JSON.parse(await handler.readFile(metadataPath))
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
- // purge cache if a metadata file has been deleted
550
- if (mustInvalidateCache) {
551
- await handler.unlink(vmDir + '/cache.json.gz')
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.1",
11
+ "version": "0.29.2",
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.2"
55
+ "@xen-orchestra/xapi": "^1.5.3"
56
56
  },
57
57
  "license": "AGPL-3.0-or-later",
58
58
  "author": {