From 64a6b624b75e3179741bacf6eb9c55c8343f936f Mon Sep 17 00:00:00 2001 From: Florent Beauchamp Date: Mon, 4 Jul 2022 09:35:12 +0200 Subject: [PATCH 1/2] feat(cleanVm): add recovery method for duplicated vhd uuid containing the same data --- @xen-orchestra/backups/_cleanVm.js | 14 +++++++++++ CHANGELOG.unreleased.md | 2 ++ .../vhd-lib/Vhd/VhdAbstract.integ.spec.js | 23 +++++++++++++++++++ packages/vhd-lib/Vhd/VhdAbstract.js | 21 +++++++++++++++++ 4 files changed, 60 insertions(+) diff --git a/@xen-orchestra/backups/_cleanVm.js b/@xen-orchestra/backups/_cleanVm.js index f8ec789069d..e8332faf55b 100644 --- a/@xen-orchestra/backups/_cleanVm.js +++ b/@xen-orchestra/backups/_cleanVm.js @@ -189,6 +189,7 @@ exports.cleanVm = async function cleanVm( const vhdsToJSons = new Set() const vhdParents = { __proto__: null } const vhdChildren = { __proto__: null } + const vhdIds = new Map() const { vhds, interruptedVhds, aliases } = await listVhds(handler, vmDir) @@ -208,6 +209,19 @@ exports.cleanVm = async function cleanVm( } vhdChildren[parent] = path } + const duplicate = vhdIds.get(vhd.footer.uuid) + if (duplicate) { + logWarn('uuid is duplicated', { uuid: vhd.footer.uuid }) + // uuid is already present + if (duplicate.contains(vhd)) { + logWarn(`should delete ${path}`) + } else if (vhd.contains(duplicate)) { + logWarn(`should delete ${duplicate._path}`) + } else { + logWarn(`same ids but different content`) + } + } + vhdIds.set(vhd.footer.uuid, vhd) }) } catch (error) { vhds.delete(path) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index ff06bf79475..045b13c0cc9 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -29,6 +29,8 @@ +- @xen-orchestra/backups minor - xo-web minor +- vhd-lib minor diff --git a/packages/vhd-lib/Vhd/VhdAbstract.integ.spec.js b/packages/vhd-lib/Vhd/VhdAbstract.integ.spec.js index e9b0e251848..3cd6d311017 100644 --- a/packages/vhd-lib/Vhd/VhdAbstract.integ.spec.js +++ b/packages/vhd-lib/Vhd/VhdAbstract.integ.spec.js @@ -275,3 +275,26 @@ it('can stream content', async () => { } }) }) + +it('can check vhd contained in on another', async () => { + const rawFile = `${tempDir}/contained` + await createRandomFile(rawFile, 4) + const containedVhdFileName = `${tempDir}/contained.vhd` + await convertFromRawToVhd(rawFile, containedVhdFileName) + + const after = `${tempDir}/after` + await createRandomFile(after, 4) + + fs.appendFile(rawFile, await fs.readFile(after)) + + const cnotainerVhdFileName = `${tempDir}/container.vhd` + await convertFromRawToVhd(rawFile, cnotainerVhdFileName) + + await Disposable.use(async function* () { + const handler = yield getSyncedHandler({ url: 'file://' + tempDir }) + const contained = yield openVhd(handler, 'contained.vhd') + const container = yield openVhd(handler, 'container.vhd') + expect(await contained.contains(container)).toEqual(false) + expect(await container.contains(contained)).toEqual(true) + }) +}) diff --git a/packages/vhd-lib/Vhd/VhdAbstract.js b/packages/vhd-lib/Vhd/VhdAbstract.js index b16b1d53e6c..e5b10d56917 100644 --- a/packages/vhd-lib/Vhd/VhdAbstract.js +++ b/packages/vhd-lib/Vhd/VhdAbstract.js @@ -334,4 +334,25 @@ exports.VhdAbstract = class VhdAbstract { stream.length = footer.currentSize return stream } + + /* + * check if all the data of a child are already contained in this vhd + */ + + async contains(child) { + await this.readBlockAllocationTable() + await child.readBlockAllocationTable() + for await (const block of child.blocks()) { + const { id, data: childData } = block + // block is in child not in parent + if (!this.containsBlock(id)) { + return false + } + const { data: parentData } = await this.readBlock(id) + if (!childData.equals(parentData)) { + return false + } + } + return true + } } From 13d9edf4ce556a1469abf18b63be932b0c3375cf Mon Sep 17 00:00:00 2001 From: Florent Beauchamp Date: Mon, 4 Jul 2022 14:16:44 +0200 Subject: [PATCH 2/2] fix --- @xen-orchestra/backups/_cleanVm.js | 16 +++++++++------- packages/vhd-lib/Vhd/VhdAbstract.js | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/@xen-orchestra/backups/_cleanVm.js b/@xen-orchestra/backups/_cleanVm.js index e8332faf55b..965b44e122f 100644 --- a/@xen-orchestra/backups/_cleanVm.js +++ b/@xen-orchestra/backups/_cleanVm.js @@ -189,7 +189,7 @@ exports.cleanVm = async function cleanVm( const vhdsToJSons = new Set() const vhdParents = { __proto__: null } const vhdChildren = { __proto__: null } - const vhdIds = new Map() + const vhdById = new Map() const { vhds, interruptedVhds, aliases } = await listVhds(handler, vmDir) @@ -209,19 +209,18 @@ exports.cleanVm = async function cleanVm( } vhdChildren[parent] = path } - const duplicate = vhdIds.get(vhd.footer.uuid) - if (duplicate) { + const duplicate = vhdById.get(vhd.footer.uuid) + if (duplicate !== undefined) { logWarn('uuid is duplicated', { uuid: vhd.footer.uuid }) - // uuid is already present - if (duplicate.contains(vhd)) { + if (duplicate.containsAllDataOf(vhd)) { logWarn(`should delete ${path}`) - } else if (vhd.contains(duplicate)) { + } else if (vhd.containsAllDataOf(duplicate)) { logWarn(`should delete ${duplicate._path}`) } else { logWarn(`same ids but different content`) } } - vhdIds.set(vhd.footer.uuid, vhd) + vhdById.set(vhd.footer.uuid, vhd) }) } catch (error) { vhds.delete(path) @@ -232,6 +231,9 @@ exports.cleanVm = async function cleanVm( } } }) + // the vhd are closed at the end of the disposable + // it's unsafe to use them later + vhdById.clear() // remove interrupted merge states for missing VHDs for (const interruptedVhd of interruptedVhds.keys()) { diff --git a/packages/vhd-lib/Vhd/VhdAbstract.js b/packages/vhd-lib/Vhd/VhdAbstract.js index e5b10d56917..dd20a7d4aa7 100644 --- a/packages/vhd-lib/Vhd/VhdAbstract.js +++ b/packages/vhd-lib/Vhd/VhdAbstract.js @@ -339,7 +339,7 @@ exports.VhdAbstract = class VhdAbstract { * check if all the data of a child are already contained in this vhd */ - async contains(child) { + async containsAllDataOf(child) { await this.readBlockAllocationTable() await child.readBlockAllocationTable() for await (const block of child.blocks()) {