diff --git a/@xen-orchestra/backups/_cleanVm.integ.spec.js b/@xen-orchestra/backups/_cleanVm.integ.spec.js index 5d2544219..5492d17aa 100644 --- a/@xen-orchestra/backups/_cleanVm.integ.spec.js +++ b/@xen-orchestra/backups/_cleanVm.integ.spec.js @@ -14,7 +14,8 @@ const { VhdFile, Constants, VhdDirectory, VhdAbstract } = require('vhd-lib') const { checkAliases } = require('./_cleanVm') const { dirname, basename } = require('path') -let tempDir, adapter, handler, jobId, vdiId, basePath +let tempDir, adapter, handler, jobId, vdiId, basePath, relativePath +const rootPath = 'xo-vm-backups/VMUUID/' jest.setTimeout(60000) @@ -25,7 +26,8 @@ beforeEach(async () => { adapter = new RemoteAdapter(handler) jobId = uniqueId() vdiId = uniqueId() - basePath = `vdis/${jobId}/${vdiId}` + relativePath = `vdis/${jobId}/${vdiId}` + basePath = `${rootPath}/${relativePath}` await fs.mkdirp(`${tempDir}/${basePath}`) }) @@ -81,13 +83,13 @@ test('It remove broken vhd', async () => { const logInfo = message => { loggued += message } - await adapter.cleanVm('/', { remove: false, logInfo, logWarn: logInfo, lock: false }) + await adapter.cleanVm(rootPath, { remove: false, logInfo, logWarn: logInfo, lock: false }) expect(loggued).toEqual(`VHD check error`) // not removed - expect((await handler.list(basePath)).length).toEqual(1) + expect(await handler.list(basePath)).toEqual(['notReallyAVhd.vhd']) // really remove it - await adapter.cleanVm('/', { remove: true, logInfo, logWarn: () => {}, lock: false }) - expect((await handler.list(basePath)).length).toEqual(0) + await adapter.cleanVm(rootPath, { remove: true, logInfo, logWarn: () => {}, lock: false }) + expect(await handler.list(basePath)).toEqual([]) }) test('it remove vhd with missing or multiple ancestors', async () => { @@ -121,7 +123,7 @@ test('it remove vhd with missing or multiple ancestors', async () => { const logInfo = message => { loggued += message + '\n' } - await adapter.cleanVm('/', { remove: true, logInfo, logWarn: logInfo, lock: false }) + await adapter.cleanVm(rootPath, { remove: true, logInfo, logWarn: logInfo, lock: false }) const deletedOrphanVhd = loggued.match(/deleting orphan VHD/g) || [] expect(deletedOrphanVhd.length).toEqual(1) // only one vhd should have been deleted @@ -132,12 +134,12 @@ test('it remove vhd with missing or multiple ancestors', async () => { test('it remove backup meta data referencing a missing vhd in delta backup', async () => { // create a metadata file marking child and orphan as ok await handler.writeFile( - `metadata.json`, + `${rootPath}/metadata.json`, JSON.stringify({ mode: 'delta', vhds: [ - `${basePath}/orphan.vhd`, - `${basePath}/child.vhd`, + `${relativePath}/orphan.vhd`, + `${relativePath}/child.vhd`, // abandonned.json is not here ], }) @@ -160,39 +162,39 @@ test('it remove backup meta data referencing a missing vhd in delta backup', asy const logInfo = message => { loggued += message + '\n' } - await adapter.cleanVm('/', { remove: true, logInfo, logWarn: logInfo, lock: false }) + await adapter.cleanVm(rootPath, { remove: true, logInfo, logWarn: logInfo, lock: false }) let matched = loggued.match(/deleting unused VHD/g) || [] expect(matched.length).toEqual(1) // only one vhd should have been deleted // a missing vhd cause clean to remove all vhds await handler.writeFile( - `metadata.json`, + `${rootPath}/metadata.json`, JSON.stringify({ mode: 'delta', vhds: [ - `${basePath}/deleted.vhd`, // in metadata but not in vhds - `${basePath}/orphan.vhd`, - `${basePath}/child.vhd`, + `deleted.vhd`, // in metadata but not in vhds + `orphan.vhd`, + `child.vhd`, // abandonned.vhd is not here anymore ], }), { flags: 'w' } ) loggued = '' - await adapter.cleanVm('/', { remove: true, logInfo, logWarn: () => {}, lock: false }) + await adapter.cleanVm(rootPath, { remove: true, logInfo, logWarn: () => {}, lock: false }) matched = loggued.match(/deleting unused VHD/g) || [] expect(matched.length).toEqual(2) // all vhds (orphan and child ) should have been deleted }) test('it merges delta of non destroyed chain', async () => { await handler.writeFile( - `metadata.json`, + `${rootPath}/metadata.json`, JSON.stringify({ mode: 'delta', size: 12000, // a size too small vhds: [ - `${basePath}/grandchild.vhd`, // grand child should not be merged - `${basePath}/child.vhd`, + `${relativePath}/grandchild.vhd`, // grand child should not be merged + `${relativePath}/child.vhd`, // orphan is not here, he should be merged in child ], }) @@ -219,15 +221,15 @@ test('it merges delta of non destroyed chain', async () => { const logInfo = message => { loggued.push(message) } - await adapter.cleanVm('/', { remove: true, logInfo, logWarn: logInfo, lock: false }) + await adapter.cleanVm(rootPath, { remove: true, logInfo, logWarn: logInfo, lock: false }) expect(loggued[0]).toEqual(`incorrect backup size in metadata`) loggued = [] - await adapter.cleanVm('/', { remove: true, merge: true, logInfo, logWarn: () => {}, lock: false }) + await adapter.cleanVm(rootPath, { remove: true, merge: true, logInfo, logWarn: () => {}, lock: false }) const [merging] = loggued expect(merging).toEqual(`merging VHD chain`) - const metadata = JSON.parse(await handler.readFile(`metadata.json`)) + const metadata = JSON.parse(await handler.readFile(`${rootPath}/metadata.json`)) // size should be the size of children + grand children after the merge expect(metadata.size).toEqual(209920) @@ -241,11 +243,11 @@ test('it merges delta of non destroyed chain', async () => { test('it finish unterminated merge ', async () => { await handler.writeFile( - `metadata.json`, + `${rootPath}/metadata.json`, JSON.stringify({ mode: 'delta', size: 209920, - vhds: [`${basePath}/orphan.vhd`, `${basePath}/child.vhd`], + vhds: [`${relativePath}/orphan.vhd`, `${relativePath}/child.vhd`], }) ) @@ -271,7 +273,7 @@ test('it finish unterminated merge ', async () => { }) ) - await adapter.cleanVm('/', { remove: true, merge: true, logWarn: () => {}, lock: false }) + await adapter.cleanVm(rootPath, { remove: true, merge: true, logWarn: () => {}, lock: false }) // merging is already tested in vhd-lib, don't retest it here (and theses vhd are as empty as my stomach at 12h12) // only check deletion @@ -367,20 +369,20 @@ describe('tests multiple combination ', () => { // the metadata file await handler.writeFile( - `metadata.json`, + `${rootPath}/metadata.json`, JSON.stringify({ mode: 'delta', vhds: [ - `${basePath}/grandchild.vhd` + (useAlias ? '.alias.vhd' : ''), // grand child should not be merged - `${basePath}/child.vhd` + (useAlias ? '.alias.vhd' : ''), - `${basePath}/clean.vhd` + (useAlias ? '.alias.vhd' : ''), + `${relativePath}/grandchild.vhd` + (useAlias ? '.alias.vhd' : ''), // grand child should not be merged + `${relativePath}/child.vhd` + (useAlias ? '.alias.vhd' : ''), + `${relativePath}/clean.vhd` + (useAlias ? '.alias.vhd' : ''), ], }) ) - await adapter.cleanVm('/', { remove: true, merge: true, logWarn: () => {}, lock: false }) + await adapter.cleanVm(rootPath, { remove: true, merge: true, logWarn: () => {}, lock: false }) - const metadata = JSON.parse(await handler.readFile(`metadata.json`)) + const metadata = JSON.parse(await handler.readFile(`${rootPath}/metadata.json`)) // size should be the size of children + grand children + clean after the merge expect(metadata.size).toEqual(vhdMode === 'file' ? 314880 : undefined) @@ -414,7 +416,7 @@ describe('tests multiple combination ', () => { test('it cleans orphan merge states ', async () => { await handler.writeFile(`${basePath}/.orphan.vhd.merge.json`, '') - await adapter.cleanVm('/', { remove: true, logWarn: () => {}, lock: false }) + await adapter.cleanVm(rootPath, { remove: true, logWarn: () => {}, lock: false }) expect(await handler.list(basePath)).toEqual([]) }) diff --git a/@xen-orchestra/fs/src/abstract.spec.js b/@xen-orchestra/fs/src/abstract.spec.js index 8dcfa2a9b..a6612e738 100644 --- a/@xen-orchestra/fs/src/abstract.spec.js +++ b/@xen-orchestra/fs/src/abstract.spec.js @@ -122,14 +122,14 @@ describe('encryption', () => { }) it('sync should NOT create metadata if missing (not encrypted)', async () => { handler = getHandler({ url: `file://${dir}` }) - await handler._checkMetadata() + await handler.sync() expect(await fs.readdir(dir)).toEqual([]) }) it('sync should create metadata if missing (encrypted)', async () => { handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd00"` }) - await handler._checkMetadata() + await handler.sync() expect(await fs.readdir(dir)).toEqual(['encryption.json', 'metadata.json']) @@ -140,11 +140,11 @@ describe('encryption', () => { }) it('sync should not modify existing metadata', async () => { - handler = getHandler({ url: `file://${dir}` }) await fs.writeFile(`${dir}/encryption.json`, `{"algorithm": "none"}`) await fs.writeFile(`${dir}/metadata.json`, `{"random": "NOTSORANDOM"}`) + handler = getHandler({ url: `file://${dir}` }) - await handler._checkMetadata() + await handler.sync() const encryption = JSON.parse(await fs.readFile(`${dir}/encryption.json`, 'utf-8')) expect(encryption.algorithm).toEqual('none') @@ -154,16 +154,16 @@ describe('encryption', () => { it('should modify metadata if empty', async () => { handler = getHandler({ url: `file://${dir}` }) - await handler._checkMetadata() + await handler.sync() await handler.forget() // nothing created without encryption handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd00"` }) - await handler._checkMetadata() + await handler.sync() let encryption = JSON.parse(await fs.readFile(`${dir}/encryption.json`, 'utf-8')) expect(encryption.algorithm).toEqual(DEFAULT_ENCRYPTION_ALGORITHM) await handler.forget() handler = getHandler({ url: `file://${dir}` }) - await handler._checkMetadata() + await handler.sync() encryption = JSON.parse(await fs.readFile(`${dir}/encryption.json`, 'utf-8')) expect(encryption.algorithm).toEqual('none') }) @@ -175,7 +175,7 @@ describe('encryption', () => { await fs.writeFile(`${dir}/metadata.json`, encryptor.encryptData(`{"random": "NOTSORANDOM"}`)) handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd91"` }) - await handler._checkMetadata() + await handler.sync() const encryption = JSON.parse(await fs.readFile(`${dir}/encryption.json`, 'utf-8')) expect(encryption.algorithm).toEqual(DEFAULT_ENCRYPTION_ALGORITHM) @@ -191,12 +191,12 @@ describe('encryption', () => { // different key but empty remote => ok handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd00"` }) - await expect(handler._checkMetadata()).resolves.not.toThrowError() + await expect(handler.sync()).resolves.not.toThrowError() // rmote is now non empty : can't modify key anymore await fs.writeFile(`${dir}/nonempty.json`, 'content') handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd10"` }) - await expect(handler._checkMetadata()).rejects.toThrowError() + await expect(handler.sync()).rejects.toThrowError() }) it('sync should fail when changing algorithm', async () => { @@ -210,6 +210,6 @@ describe('encryption', () => { await fs.writeFile(`${dir}/nonempty.json`, 'content') handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd91"` }) - await expect(handler._checkMetadata()).rejects.toThrowError() + await expect(handler.sync()).rejects.toThrowError() }) }) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index cc2850db4..1d4a33cef 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -36,9 +36,11 @@ - @vates/otp major - @vates/predicates minor - @vates/read-chunk patch +- @xen-orchestra/backups patch - @xen-orchestra/fs minor - @xen-orchestra/log minor -- @xen-orchestra/mixins patch +- vhd-cli patch +- vhd-lib patch - xo-remote-parser patch - xo-server minor - xo-server-transport-nagios patch diff --git a/packages/vhd-lib/Vhd/VhdDirectory.integ.spec.js b/packages/vhd-lib/Vhd/VhdDirectory.integ.spec.js index 27a0b4c92..2266a1794 100644 --- a/packages/vhd-lib/Vhd/VhdDirectory.integ.spec.js +++ b/packages/vhd-lib/Vhd/VhdDirectory.integ.spec.js @@ -5,22 +5,28 @@ const rimraf = require('rimraf') const tmp = require('tmp') const fs = require('fs-extra') -const { getHandler, getSyncedHandler } = require('@xen-orchestra/fs') +const { getSyncedHandler } = require('@xen-orchestra/fs') const { Disposable, pFromCallback } = require('promise-toolbox') const { openVhd, VhdDirectory } = require('../') const { createRandomFile, convertFromRawToVhd, convertToVhdDirectory } = require('../tests/utils') let tempDir = null +let handler +let disposeHandler jest.setTimeout(60000) beforeEach(async () => { tempDir = await pFromCallback(cb => tmp.dir(cb)) + const d = await getSyncedHandler({ url: `file://${tempDir}` }) + handler = d.value + disposeHandler = d.dispose }) afterEach(async () => { await pFromCallback(cb => rimraf(tempDir, cb)) + disposeHandler() }) test('Can coalesce block', async () => { @@ -45,12 +51,11 @@ test('Can coalesce block', async () => { await convertToVhdDirectory(childRawDirectoryName, childDirectoryFileName, childDirectoryName) await Disposable.use(async function* () { - const handler = getHandler({ url: 'file://' }) - const parentVhd = yield openVhd(handler, parentDirectoryName, { flags: 'w' }) + const parentVhd = yield openVhd(handler, 'parent.dir.vhd', { flags: 'w' }) await parentVhd.readBlockAllocationTable() - const childFileVhd = yield openVhd(handler, childFileName) + const childFileVhd = yield openVhd(handler, 'childFile.vhd') await childFileVhd.readBlockAllocationTable() - const childDirectoryVhd = yield openVhd(handler, childDirectoryName) + const childDirectoryVhd = yield openVhd(handler, 'childDir.vhd') await childDirectoryVhd.readBlockAllocationTable() let childBlockData = (await childDirectoryVhd.readBlock(0)).data @@ -83,7 +88,6 @@ test('compressed blocks and metadata works', async () => { await createRandomFile(rawFileName, initalSize) await convertFromRawToVhd(rawFileName, vhdName) await Disposable.use(async function* () { - const handler = yield getSyncedHandler({ url: `file://${tempDir}` }) const vhd = yield openVhd(handler, 'parent.vhd') await vhd.readBlockAllocationTable() const compressedVhd = yield VhdDirectory.create(handler, 'compressed.vhd', { compression: 'gzip' }) diff --git a/packages/vhd-lib/Vhd/VhdFile.integ.spec.js b/packages/vhd-lib/Vhd/VhdFile.integ.spec.js index ceb12cf7f..763f9414f 100644 --- a/packages/vhd-lib/Vhd/VhdFile.integ.spec.js +++ b/packages/vhd-lib/Vhd/VhdFile.integ.spec.js @@ -7,7 +7,7 @@ const fs = require('fs-extra') const getStream = require('get-stream') const rimraf = require('rimraf') const tmp = require('tmp') -const { getHandler } = require('@xen-orchestra/fs') +const { getSyncedHandler } = require('@xen-orchestra/fs') const { Disposable, pFromCallback } = require('promise-toolbox') const { randomBytes } = require('crypto') @@ -24,15 +24,22 @@ const { } = require('../tests/utils') let tempDir = null +let handler +let disposeHandler jest.setTimeout(60000) beforeEach(async () => { tempDir = await pFromCallback(cb => tmp.dir(cb)) + + const d = await getSyncedHandler({ url: `file://${tempDir}` }) + handler = d.value + disposeHandler = d.dispose }) afterEach(async () => { await pFromCallback(cb => rimraf(tempDir, cb)) + disposeHandler() }) test('respect the checkSecondFooter flag', async () => { @@ -42,8 +49,6 @@ test('respect the checkSecondFooter flag', async () => { const vhdFileName = `${tempDir}/randomfile.vhd` await convertFromRawToVhd(rawFileName, vhdFileName) - const handler = getHandler({ url: `file://${tempDir}` }) - const size = await handler.getSize('randomfile.vhd') const fd = await handler.openFile('randomfile.vhd', 'r+') const buffer = Buffer.alloc(512, 0) @@ -64,9 +69,8 @@ test('blocks can be moved', async () => { await createRandomFile(rawFileName, initalSize) const vhdFileName = `${tempDir}/randomfile.vhd` await convertFromRawToVhd(rawFileName, vhdFileName) - const handler = getHandler({ url: 'file://' }) - const originalSize = await handler.getSize(rawFileName) - const newVhd = new VhdFile(handler, vhdFileName) + const originalSize = await handler.getSize('randomfile') + const newVhd = new VhdFile(handler, 'randomfile.vhd') await newVhd.readHeaderAndFooter() await newVhd.readBlockAllocationTable() await newVhd._freeFirstBlockSpace(8000000) @@ -79,8 +83,7 @@ test('the BAT MSB is not used for sign', async () => { const randomBuffer = await pFromCallback(cb => randomBytes(SECTOR_SIZE, cb)) const emptyFileName = `${tempDir}/empty.vhd` await execa('qemu-img', ['create', '-fvpc', emptyFileName, '1.8T']) - const handler = getHandler({ url: 'file://' }) - const vhd = new VhdFile(handler, emptyFileName) + const vhd = new VhdFile(handler, 'empty.vhd') await vhd.readHeaderAndFooter() await vhd.readBlockAllocationTable() // we want the bit 31 to be on, to prove it's not been used for sign @@ -98,7 +101,7 @@ test('the BAT MSB is not used for sign', async () => { const recoveredFileName = `${tempDir}/recovered` const recoveredFile = await fs.open(recoveredFileName, 'w') try { - const vhd2 = new VhdFile(handler, emptyFileName) + const vhd2 = new VhdFile(handler, 'empty.vhd') await vhd2.readHeaderAndFooter() await vhd2.readBlockAllocationTable() for (let i = 0; i < vhd.header.maxTableEntries; i++) { @@ -126,9 +129,8 @@ test('writeData on empty file', async () => { await createRandomFile(rawFileName, mbOfRandom) await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M']) const randomData = await fs.readFile(rawFileName) - const handler = getHandler({ url: 'file://' }) - const originalSize = await handler.getSize(rawFileName) - const newVhd = new VhdFile(handler, emptyFileName) + const originalSize = await handler.getSize('randomfile') + const newVhd = new VhdFile(handler, 'empty.vhd') await newVhd.readHeaderAndFooter() await newVhd.readBlockAllocationTable() await newVhd.writeData(0, randomData) @@ -145,9 +147,8 @@ test('writeData in 2 non-overlaping operations', async () => { await createRandomFile(rawFileName, mbOfRandom) await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M']) const randomData = await fs.readFile(rawFileName) - const handler = getHandler({ url: 'file://' }) - const originalSize = await handler.getSize(rawFileName) - const newVhd = new VhdFile(handler, emptyFileName) + const originalSize = await handler.getSize('randomfile') + const newVhd = new VhdFile(handler, 'empty.vhd') await newVhd.readHeaderAndFooter() await newVhd.readBlockAllocationTable() const splitPointSectors = 2 @@ -165,9 +166,8 @@ test('writeData in 2 overlaping operations', async () => { await createRandomFile(rawFileName, mbOfRandom) await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M']) const randomData = await fs.readFile(rawFileName) - const handler = getHandler({ url: 'file://' }) - const originalSize = await handler.getSize(rawFileName) - const newVhd = new VhdFile(handler, emptyFileName) + const originalSize = await handler.getSize('randomfile') + const newVhd = new VhdFile(handler, 'empty.vhd') await newVhd.readHeaderAndFooter() await newVhd.readBlockAllocationTable() const endFirstWrite = 3 @@ -185,9 +185,8 @@ test('BAT can be extended and blocks moved', async () => { const vhdFileName = `${tempDir}/randomfile.vhd` await createRandomFile(rawFileName, initalSize) await convertFromRawToVhd(rawFileName, vhdFileName) - const handler = getHandler({ url: 'file://' }) - const originalSize = await handler.getSize(rawFileName) - const newVhd = new VhdFile(handler, vhdFileName) + const originalSize = await handler.getSize('randomfile') + const newVhd = new VhdFile(handler, 'randomfile.vhd') await newVhd.readHeaderAndFooter() await newVhd.readBlockAllocationTable() await newVhd.ensureBatSize(2000) @@ -214,12 +213,11 @@ test('Can coalesce block', async () => { await convertToVhdDirectory(childRawDirectoryName, childDirectoryFileName, childDirectoryName) await Disposable.use(async function* () { - const handler = getHandler({ url: 'file://' }) - const parentVhd = yield openVhd(handler, parentFileName, { flags: 'r+' }) + const parentVhd = yield openVhd(handler, 'parent.vhd', { flags: 'r+' }) await parentVhd.readBlockAllocationTable() - const childFileVhd = yield openVhd(handler, childFileName) + const childFileVhd = yield openVhd(handler, 'childFile.vhd') await childFileVhd.readBlockAllocationTable() - const childDirectoryVhd = yield openVhd(handler, childDirectoryName) + const childDirectoryVhd = yield openVhd(handler, 'childDir.vhd') await childDirectoryVhd.readBlockAllocationTable() await parentVhd.mergeBlock(childFileVhd, 0) diff --git a/packages/vhd-lib/merge.integ.spec.js b/packages/vhd-lib/merge.integ.spec.js index f4c34fbf5..147d9ed96 100644 --- a/packages/vhd-lib/merge.integ.spec.js +++ b/packages/vhd-lib/merge.integ.spec.js @@ -5,7 +5,7 @@ const fs = require('fs-extra') const rimraf = require('rimraf') const tmp = require('tmp') -const { getHandler } = require('@xen-orchestra/fs') +const { getSyncedHandler } = require('@xen-orchestra/fs') const { pFromCallback } = require('promise-toolbox') const { VhdFile, chainVhd } = require('./index') @@ -14,15 +14,21 @@ const { _cleanupVhds: cleanupVhds, mergeVhdChain } = require('./merge') const { checkFile, createRandomFile, convertFromRawToVhd } = require('./tests/utils') let tempDir = null - +let handler +let disposeHandler jest.setTimeout(60000) beforeEach(async () => { tempDir = await pFromCallback(cb => tmp.dir(cb)) + + const d = await getSyncedHandler({ url: `file://${tempDir}` }) + handler = d.value + disposeHandler = d.dispose }) afterEach(async () => { await pFromCallback(cb => rimraf(tempDir, cb)) + disposeHandler() }) test('merge works in normal cases', async () => { @@ -32,7 +38,6 @@ test('merge works in normal cases', async () => { const childRandomFileName = `small_randomfile` const parentFileName = `parent.vhd` const child1FileName = `child1.vhd` - const handler = getHandler({ url: `file://${tempDir}` }) await createRandomFile(`${tempDir}/${parentRandomFileName}`, mbOfFather) await convertFromRawToVhd(`${tempDir}/${parentRandomFileName}`, `${tempDir}/${parentFileName}`) @@ -70,7 +75,6 @@ test('it can resume a simple merge ', async () => { const mbOfChildren = 4 const parentRandomFileName = `${tempDir}/randomfile` const childRandomFileName = `${tempDir}/small_randomfile` - const handler = getHandler({ url: `file://${tempDir}` }) await createRandomFile(`${tempDir}/randomfile`, mbOfFather) await convertFromRawToVhd(`${tempDir}/randomfile`, `${tempDir}/parent.vhd`) @@ -169,29 +173,28 @@ test('it can resume a multiple merge ', async () => { const parentFileName = `${tempDir}/parent.vhd` const childFileName = `${tempDir}/child.vhd` const grandChildFileName = `${tempDir}/grandchild.vhd` - const handler = getHandler({ url: 'file://' }) await createRandomFile(parentRandomFileName, mbOfFather) await convertFromRawToVhd(parentRandomFileName, parentFileName) await createRandomFile(childRandomFileName, mbOfChildren) await convertFromRawToVhd(childRandomFileName, childFileName) - await chainVhd(handler, parentFileName, handler, childFileName, true) + await chainVhd(handler, 'parent.vhd', handler, 'child.vhd', true) await createRandomFile(grandChildRandomFileName, mbOfGrandChildren) await convertFromRawToVhd(grandChildRandomFileName, grandChildFileName) - await chainVhd(handler, childFileName, handler, grandChildFileName, true) + await chainVhd(handler, 'child.vhd', handler, 'grandchild.vhd', true) - const parentVhd = new VhdFile(handler, parentFileName) + const parentVhd = new VhdFile(handler, 'parent.vhd') await parentVhd.readHeaderAndFooter() - const childVhd = new VhdFile(handler, childFileName) + const childVhd = new VhdFile(handler, 'child.vhd') await childVhd.readHeaderAndFooter() - const grandChildVhd = new VhdFile(handler, grandChildFileName) + const grandChildVhd = new VhdFile(handler, 'grandchild.vhd') await grandChildVhd.readHeaderAndFooter() await handler.writeFile( - `${tempDir}/.parent.vhd.merge.json`, + `.parent.vhd.merge.json`, JSON.stringify({ parent: { header: parentVhd.header.checksum, @@ -205,12 +208,12 @@ test('it can resume a multiple merge ', async () => { // should fail since the merge state file has only data of parent and child await expect( - async () => await mergeVhdChain(handler, [parentFileName, childFileName, grandChildFileName]) + async () => await mergeVhdChain(handler, ['parent.vhd', 'child.vhd', 'grandchild.vhd']) ).rejects.toThrow() // merge - await handler.unlink(`${tempDir}/.parent.vhd.merge.json`) + await handler.unlink(`.parent.vhd.merge.json`) await handler.writeFile( - `${tempDir}/.parent.vhd.merge.json`, + `.parent.vhd.merge.json`, JSON.stringify({ parent: { header: parentVhd.header.checksum, @@ -219,11 +222,11 @@ test('it can resume a multiple merge ', async () => { header: grandChildVhd.header.checksum, }, currentBlock: 1, - childPath: [childVhd, grandChildVhd], + childPath: ['child.vhd', 'grandchild.vhd'], }) ) // it should succeed - await mergeVhdChain(handler, [parentFileName, childFileName, grandChildFileName]) + await mergeVhdChain(handler, ['parent.vhd', 'child.vhd', 'grandchild.vhd']) }) test('it merge multiple child in one pass ', async () => { @@ -236,25 +239,25 @@ test('it merge multiple child in one pass ', async () => { const parentFileName = `${tempDir}/parent.vhd` const childFileName = `${tempDir}/child.vhd` const grandChildFileName = `${tempDir}/grandchild.vhd` - const handler = getHandler({ url: 'file://' }) + await createRandomFile(parentRandomFileName, mbOfFather) await convertFromRawToVhd(parentRandomFileName, parentFileName) await createRandomFile(childRandomFileName, mbOfChildren) await convertFromRawToVhd(childRandomFileName, childFileName) - await chainVhd(handler, parentFileName, handler, childFileName, true) + await chainVhd(handler, 'parent.vhd', handler, 'child.vhd', true) await createRandomFile(grandChildRandomFileName, mbOfGrandChildren) await convertFromRawToVhd(grandChildRandomFileName, grandChildFileName) - await chainVhd(handler, childFileName, handler, grandChildFileName, true) + await chainVhd(handler, 'child.vhd', handler, 'grandchild.vhd', true) // merge - await mergeVhdChain(handler, [parentFileName, childFileName, grandChildFileName]) + await mergeVhdChain(handler, ['parent.vhd', 'child.vhd', 'grandchild.vhd']) // check that vhd is still valid await checkFile(grandChildFileName) - const parentVhd = new VhdFile(handler, grandChildFileName) + const parentVhd = new VhdFile(handler, 'grandchild.vhd') await parentVhd.readHeaderAndFooter() await parentVhd.readBlockAllocationTable() @@ -277,8 +280,6 @@ test('it merge multiple child in one pass ', async () => { }) test('it cleans vhd mergedfiles', async () => { - const handler = getHandler({ url: `file://${tempDir}` }) - await handler.writeFile('parent', 'parentData') await handler.writeFile('child1', 'child1Data') await handler.writeFile('child2', 'child2Data') diff --git a/packages/vhd-lib/openVhd.integ.spec.js b/packages/vhd-lib/openVhd.integ.spec.js index f6b84102c..2b6ba4646 100644 --- a/packages/vhd-lib/openVhd.integ.spec.js +++ b/packages/vhd-lib/openVhd.integ.spec.js @@ -4,6 +4,7 @@ const rimraf = require('rimraf') const tmp = require('tmp') +const fs = require('node:fs/promises') const { getSyncedHandler } = require('@xen-orchestra/fs') const { Disposable, pFromCallback } = require('promise-toolbox') @@ -31,13 +32,13 @@ test('It opens a vhd file ( alias or not)', async () => { const vhdFileName = `${tempDir}/randomfile.vhd` await convertFromRawToVhd(rawFileName, vhdFileName) await Disposable.use(async function* () { - const handler = yield getSyncedHandler({ url: 'file://' }) - const vhd = yield openVhd(handler, vhdFileName) + const handler = yield getSyncedHandler({ url: `file://${tempDir}` }) + const vhd = yield openVhd(handler, 'randomfile.vhd') expect(vhd.header.cookie).toEqual('cxsparse') expect(vhd.footer.cookie).toEqual('conectix') - const aliasFileName = `${tempDir}/out.alias.vhd` - await VhdAbstract.createAlias(handler, aliasFileName, vhdFileName) + const aliasFileName = `out.alias.vhd` + await VhdAbstract.createAlias(handler, aliasFileName, 'randomfile.vhd') const alias = yield openVhd(handler, aliasFileName) expect(alias.header.cookie).toEqual('cxsparse') expect(alias.footer.cookie).toEqual('conectix') @@ -50,15 +51,77 @@ test('It opens a vhd directory', async () => { await createRandomVhdDirectory(vhdDirectory, initalSize) await Disposable.use(async function* () { - const handler = yield getSyncedHandler({ url: 'file://' }) - const vhd = yield openVhd(handler, vhdDirectory) + const handler = yield getSyncedHandler({ url: `file://${tempDir}` }) + const vhd = yield openVhd(handler, 'randomfile.dir') expect(vhd.header.cookie).toEqual('cxsparse') expect(vhd.footer.cookie).toEqual('conectix') - const aliasFileName = `${tempDir}/out.alias.vhd` - await VhdAbstract.createAlias(handler, aliasFileName, vhdDirectory) + const aliasFileName = `out.alias.vhd` + await VhdAbstract.createAlias(handler, aliasFileName, 'randomfile.dir') const alias = yield openVhd(handler, aliasFileName) expect(alias.header.cookie).toEqual('cxsparse') expect(alias.footer.cookie).toEqual('conectix') }) }) + +test('It fails correctly when opening a broken vhd', async () => { + const initalSize = 4 + + // emtpy file + await expect( + Disposable.use(async function* () { + const handler = yield getSyncedHandler({ url: `file://${tempDir}` }) + yield openVhd(handler, 'randomfile.vhd') + }) + ).rejects.toThrow() + + const rawFileName = `${tempDir}/randomfile.vhd` + await createRandomFile(rawFileName, initalSize) + // broken file + await expect( + Disposable.use(async function* () { + const handler = yield getSyncedHandler({ url: `file://${tempDir}` }) + yield openVhd(handler, 'randomfile.vhd') + }) + ).rejects.toThrow() + + // empty dir + await fs.mkdir(`${tempDir}/dir.vhd`) + await expect( + Disposable.use(async function* () { + const handler = yield getSyncedHandler({ url: `file://${tempDir}` }) + const vhd = yield openVhd(handler, 'dir.vhd') + await vhd.readBlockAllocationTable() + }) + ).rejects.toThrow() + // dir with missing parts + await createRandomVhdDirectory(`${tempDir}/dir.vhd`, initalSize) + + const targets = ['header', 'footer', 'bat'] + for (const target of targets) { + await fs.rename(`${tempDir}/dir.vhd/${target}`, `${tempDir}/dir.vhd/moved`) + await expect( + Disposable.use(async function* () { + const handler = yield getSyncedHandler({ url: `file://${tempDir}` }) + const vhd = yield openVhd(handler, 'dir.vhd') + await vhd.readBlockAllocationTable() + }) + ).rejects.toThrow() + await fs.rename(`${tempDir}/dir.vhd/moved`, `${tempDir}/dir.vhd/${target}`) + } +}) + +test('It fails correctly when opening a vhdfile on an encrypted remote', async () => { + const initalSize = 4 + const rawFileName = `${tempDir}/randomfile.vhd` + await expect( + Disposable.use(async function* () { + const handler = yield getSyncedHandler({ + url: `file://${tempDir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd00"`, + }) + + await createRandomFile(rawFileName, initalSize) + yield openVhd(handler, 'randomfile.vhd') + }) + ).rejects.toThrow() +})