diff --git a/@xen-orchestra/backups/_cleanVm.integ.spec.js b/@xen-orchestra/backups/_cleanVm.integ.spec.js index 5ed5d2b8f15..0d3a1b1989a 100644 --- a/@xen-orchestra/backups/_cleanVm.integ.spec.js +++ b/@xen-orchestra/backups/_cleanVm.integ.spec.js @@ -9,6 +9,7 @@ const crypto = require('crypto') const { RemoteAdapter } = require('./RemoteAdapter') const { VHDFOOTER, VHDHEADER } = require('./tests.fixtures.js') const { VhdFile, Constants, VhdDirectory, VhdAbstract } = require('vhd-lib') +const { checkAliases } = require('./_cleanVm') const { dirname, basename } = require('path') let tempDir, adapter, handler, jobId, vdiId, basePath @@ -406,3 +407,23 @@ describe('tests multiple combination ', () => { } } }) + +test('check Aliases should work alone', async () => { + await handler.mkdir('vhds') + await handler.mkdir('vhds/data') + await generateVhd(`vhds/data/ok.vhd`) + await VhdAbstract.createAlias(handler, 'vhds/ok.alias.vhd', 'vhds/data/ok.vhd') + + await VhdAbstract.createAlias(handler, 'vhds/missingData.alias.vhd', 'vhds/data/nonexistent.vhd') + + await generateVhd(`vhds/data/missingalias.vhd`) + + await checkAliases(['vhds/missingData.alias.vhd', 'vhds/ok.alias.vhd'], 'vhds/data', { remove: true, handler }) + + // only ok have suvived + const alias = (await handler.list('vhds')).filter(f => f.endsWith('.vhd')) + expect(alias.length).toEqual(1) + + const data = await handler.list('vhds/data') + expect(data.length).toEqual(1) +}) diff --git a/@xen-orchestra/backups/_cleanVm.js b/@xen-orchestra/backups/_cleanVm.js index a58973dda4f..f2f551f4d03 100644 --- a/@xen-orchestra/backups/_cleanVm.js +++ b/@xen-orchestra/backups/_cleanVm.js @@ -1,7 +1,7 @@ const assert = require('assert') const sum = require('lodash/sum') const { asyncMap } = require('@xen-orchestra/async-map') -const { Constants, mergeVhd, openVhd, VhdAbstract, VhdFile } = require('vhd-lib') +const { Constants, isVhdAlias, mergeVhd, openVhd, VhdAbstract, VhdFile, resolveAlias } = require('vhd-lib') const { dirname, resolve } = require('path') const { DISK_TYPES } = Constants const { isMetadataFile, isVhdFile, isXvaFile, isXvaSumFile } = require('./_backupType.js') @@ -82,6 +82,7 @@ async function mergeVhdChain(chain, { handler, onLog, remove, merge }) { ) clearInterval(handle) + onLog(`merging ${child} into ${parent} done`) await Promise.all([ VhdAbstract.rename(handler, parent, child), @@ -103,6 +104,7 @@ const noop = Function.prototype const INTERRUPTED_VHDS_REG = /^(?:(.+)\/)?\.(.+)\.merge.json$/ const listVhds = async (handler, vmDir) => { const vhds = [] + const aliases = {} const interruptedVhds = new Set() await asyncMap( @@ -120,7 +122,7 @@ const listVhds = async (handler, vmDir) => { filter: file => isVhdFile(file) || INTERRUPTED_VHDS_REG.test(file), prependDir: true, }) - + aliases[vdiDir] = list.filter(vhd => isVhdAlias(vhd)) list.forEach(file => { const res = INTERRUPTED_VHDS_REG.exec(file) if (res === null) { @@ -134,8 +136,54 @@ const listVhds = async (handler, vmDir) => { ) ) - return { vhds, interruptedVhds } + return { vhds, interruptedVhds, aliases } +} + +async function checkAliases(aliasPaths, targetDataRepository, { handler, onLog = noop, remove = false }) { + const aliasFound = [] + for (const path of aliasPaths) { + const target = await resolveAlias(handler, path) + + if (!isVhdFile(target)) { + onLog(`Alias ${path} references a non vhd target: ${target}`) + if (remove) { + await handler.unlink(target) + await handler.unlink(path) + } + continue + } + + try { + const { dispose } = await openVhd(handler, target) + dispose() + } catch (e) { + onLog(`target ${target} of alias ${path} is missing or broken`) + if (remove) { + try { + await VhdAbstract.unlink(handler, path) + } catch(e){} + } + continue + } + + aliasFound.push(resolve('/', target)) + } + + const entries = await handler.list(targetDataRepository, { + ignoreMissing: true, + prependDir: true, + }) + + entries.forEach(async entry => { + if (!aliasFound.includes(entry)) { + onLog(`the Vhd ${entry} is not referenced by a an alias`) + if (remove) { + await VhdAbstract.unlink(handler, entry) + } + } + }) } +exports.checkAliases = checkAliases const defaultMergeLimiter = limitConcurrency(1) @@ -180,8 +228,12 @@ exports.cleanVm = async function cleanVm( } } }) - - // @todo : add check for data folder of alias not referenced in a valid alias + // check if alias are correct + // check if all vhd in data subfolder have a corresponding alias + await asyncMap(Object.keys(vhdsList.aliases), async dir => { + const aliases = vhdsList.aliases[dir] + await checkAliases(aliases, `${dir}/data`, { handler, onLog, remove }) + }) // remove VHDs with missing ancestors { diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 524e6ec0c6d..aa748f7484b 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -7,6 +7,8 @@ > Users must be able to say: “Nice enhancement, I'm eager to test it” +- [Backup] Add sanity check of alias after backup. PR [6043](https://github.com/vatesfr/xen-orchestra/pull/6043) + ### Bug fixes > Users must be able to say: “I had this issue, happy to know it's fixed”