test: rework tests following 05161bd4df

Test of cleanVm are still failin , untill we fix the error condition of cleanVm broken vhd removing

- don't use handler to / (need root to run)
- don't create file at the root of the remote (conflict with the metadata and encryption.json)
- test more unhappy paths
This commit is contained in:
Florent Beauchamp 2022-10-24 18:00:40 +02:00 committed by Julien Fontanet
parent 14e205ab69
commit 0cf6f94677
7 changed files with 176 additions and 106 deletions

View File

@ -14,7 +14,8 @@ const { VhdFile, Constants, VhdDirectory, VhdAbstract } = require('vhd-lib')
const { checkAliases } = require('./_cleanVm') const { checkAliases } = require('./_cleanVm')
const { dirname, basename } = require('path') 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) jest.setTimeout(60000)
@ -25,7 +26,8 @@ beforeEach(async () => {
adapter = new RemoteAdapter(handler) adapter = new RemoteAdapter(handler)
jobId = uniqueId() jobId = uniqueId()
vdiId = uniqueId() vdiId = uniqueId()
basePath = `vdis/${jobId}/${vdiId}` relativePath = `vdis/${jobId}/${vdiId}`
basePath = `${rootPath}/${relativePath}`
await fs.mkdirp(`${tempDir}/${basePath}`) await fs.mkdirp(`${tempDir}/${basePath}`)
}) })
@ -81,13 +83,13 @@ test('It remove broken vhd', async () => {
const logInfo = message => { const logInfo = message => {
loggued += 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`) expect(loggued).toEqual(`VHD check error`)
// not removed // not removed
expect((await handler.list(basePath)).length).toEqual(1) expect(await handler.list(basePath)).toEqual(['notReallyAVhd.vhd'])
// really remove it // really remove it
await adapter.cleanVm('/', { remove: true, logInfo, logWarn: () => {}, lock: false }) await adapter.cleanVm(rootPath, { remove: true, logInfo, logWarn: () => {}, lock: false })
expect((await handler.list(basePath)).length).toEqual(0) expect(await handler.list(basePath)).toEqual([])
}) })
test('it remove vhd with missing or multiple ancestors', async () => { 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 => { const logInfo = message => {
loggued += message + '\n' 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) || [] const deletedOrphanVhd = loggued.match(/deleting orphan VHD/g) || []
expect(deletedOrphanVhd.length).toEqual(1) // only one vhd should have been deleted 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 () => { test('it remove backup meta data referencing a missing vhd in delta backup', async () => {
// create a metadata file marking child and orphan as ok // create a metadata file marking child and orphan as ok
await handler.writeFile( await handler.writeFile(
`metadata.json`, `${rootPath}/metadata.json`,
JSON.stringify({ JSON.stringify({
mode: 'delta', mode: 'delta',
vhds: [ vhds: [
`${basePath}/orphan.vhd`, `${relativePath}/orphan.vhd`,
`${basePath}/child.vhd`, `${relativePath}/child.vhd`,
// abandonned.json is not here // 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 => { const logInfo = message => {
loggued += message + '\n' 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) || [] let matched = loggued.match(/deleting unused VHD/g) || []
expect(matched.length).toEqual(1) // only one vhd should have been deleted expect(matched.length).toEqual(1) // only one vhd should have been deleted
// a missing vhd cause clean to remove all vhds // a missing vhd cause clean to remove all vhds
await handler.writeFile( await handler.writeFile(
`metadata.json`, `${rootPath}/metadata.json`,
JSON.stringify({ JSON.stringify({
mode: 'delta', mode: 'delta',
vhds: [ vhds: [
`${basePath}/deleted.vhd`, // in metadata but not in vhds `deleted.vhd`, // in metadata but not in vhds
`${basePath}/orphan.vhd`, `orphan.vhd`,
`${basePath}/child.vhd`, `child.vhd`,
// abandonned.vhd is not here anymore // abandonned.vhd is not here anymore
], ],
}), }),
{ flags: 'w' } { flags: 'w' }
) )
loggued = '' 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) || [] matched = loggued.match(/deleting unused VHD/g) || []
expect(matched.length).toEqual(2) // all vhds (orphan and child ) should have been deleted expect(matched.length).toEqual(2) // all vhds (orphan and child ) should have been deleted
}) })
test('it merges delta of non destroyed chain', async () => { test('it merges delta of non destroyed chain', async () => {
await handler.writeFile( await handler.writeFile(
`metadata.json`, `${rootPath}/metadata.json`,
JSON.stringify({ JSON.stringify({
mode: 'delta', mode: 'delta',
size: 12000, // a size too small size: 12000, // a size too small
vhds: [ vhds: [
`${basePath}/grandchild.vhd`, // grand child should not be merged `${relativePath}/grandchild.vhd`, // grand child should not be merged
`${basePath}/child.vhd`, `${relativePath}/child.vhd`,
// orphan is not here, he should be merged in child // 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 => { const logInfo = message => {
loggued.push(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`) expect(loggued[0]).toEqual(`incorrect backup size in metadata`)
loggued = [] 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 const [merging] = loggued
expect(merging).toEqual(`merging VHD chain`) 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 // size should be the size of children + grand children after the merge
expect(metadata.size).toEqual(209920) expect(metadata.size).toEqual(209920)
@ -241,11 +243,11 @@ test('it merges delta of non destroyed chain', async () => {
test('it finish unterminated merge ', async () => { test('it finish unterminated merge ', async () => {
await handler.writeFile( await handler.writeFile(
`metadata.json`, `${rootPath}/metadata.json`,
JSON.stringify({ JSON.stringify({
mode: 'delta', mode: 'delta',
size: 209920, 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) // 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 // only check deletion
@ -367,20 +369,20 @@ describe('tests multiple combination ', () => {
// the metadata file // the metadata file
await handler.writeFile( await handler.writeFile(
`metadata.json`, `${rootPath}/metadata.json`,
JSON.stringify({ JSON.stringify({
mode: 'delta', mode: 'delta',
vhds: [ vhds: [
`${basePath}/grandchild.vhd` + (useAlias ? '.alias.vhd' : ''), // grand child should not be merged `${relativePath}/grandchild.vhd` + (useAlias ? '.alias.vhd' : ''), // grand child should not be merged
`${basePath}/child.vhd` + (useAlias ? '.alias.vhd' : ''), `${relativePath}/child.vhd` + (useAlias ? '.alias.vhd' : ''),
`${basePath}/clean.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 // size should be the size of children + grand children + clean after the merge
expect(metadata.size).toEqual(vhdMode === 'file' ? 314880 : undefined) expect(metadata.size).toEqual(vhdMode === 'file' ? 314880 : undefined)
@ -414,7 +416,7 @@ describe('tests multiple combination ', () => {
test('it cleans orphan merge states ', async () => { test('it cleans orphan merge states ', async () => {
await handler.writeFile(`${basePath}/.orphan.vhd.merge.json`, '') 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([]) expect(await handler.list(basePath)).toEqual([])
}) })

View File

@ -122,14 +122,14 @@ describe('encryption', () => {
}) })
it('sync should NOT create metadata if missing (not encrypted)', async () => { it('sync should NOT create metadata if missing (not encrypted)', async () => {
handler = getHandler({ url: `file://${dir}` }) handler = getHandler({ url: `file://${dir}` })
await handler._checkMetadata() await handler.sync()
expect(await fs.readdir(dir)).toEqual([]) expect(await fs.readdir(dir)).toEqual([])
}) })
it('sync should create metadata if missing (encrypted)', async () => { it('sync should create metadata if missing (encrypted)', async () => {
handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd00"` }) handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd00"` })
await handler._checkMetadata() await handler.sync()
expect(await fs.readdir(dir)).toEqual(['encryption.json', 'metadata.json']) expect(await fs.readdir(dir)).toEqual(['encryption.json', 'metadata.json'])
@ -140,11 +140,11 @@ describe('encryption', () => {
}) })
it('sync should not modify existing metadata', async () => { 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}/encryption.json`, `{"algorithm": "none"}`)
await fs.writeFile(`${dir}/metadata.json`, `{"random": "NOTSORANDOM"}`) 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')) const encryption = JSON.parse(await fs.readFile(`${dir}/encryption.json`, 'utf-8'))
expect(encryption.algorithm).toEqual('none') expect(encryption.algorithm).toEqual('none')
@ -154,16 +154,16 @@ describe('encryption', () => {
it('should modify metadata if empty', async () => { it('should modify metadata if empty', async () => {
handler = getHandler({ url: `file://${dir}` }) handler = getHandler({ url: `file://${dir}` })
await handler._checkMetadata() await handler.sync()
await handler.forget() await handler.forget()
// nothing created without encryption // nothing created without encryption
handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd00"` }) 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')) let encryption = JSON.parse(await fs.readFile(`${dir}/encryption.json`, 'utf-8'))
expect(encryption.algorithm).toEqual(DEFAULT_ENCRYPTION_ALGORITHM) expect(encryption.algorithm).toEqual(DEFAULT_ENCRYPTION_ALGORITHM)
await handler.forget() await handler.forget()
handler = getHandler({ url: `file://${dir}` }) handler = getHandler({ url: `file://${dir}` })
await handler._checkMetadata() await handler.sync()
encryption = JSON.parse(await fs.readFile(`${dir}/encryption.json`, 'utf-8')) encryption = JSON.parse(await fs.readFile(`${dir}/encryption.json`, 'utf-8'))
expect(encryption.algorithm).toEqual('none') expect(encryption.algorithm).toEqual('none')
}) })
@ -175,7 +175,7 @@ describe('encryption', () => {
await fs.writeFile(`${dir}/metadata.json`, encryptor.encryptData(`{"random": "NOTSORANDOM"}`)) await fs.writeFile(`${dir}/metadata.json`, encryptor.encryptData(`{"random": "NOTSORANDOM"}`))
handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd91"` }) 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')) const encryption = JSON.parse(await fs.readFile(`${dir}/encryption.json`, 'utf-8'))
expect(encryption.algorithm).toEqual(DEFAULT_ENCRYPTION_ALGORITHM) expect(encryption.algorithm).toEqual(DEFAULT_ENCRYPTION_ALGORITHM)
@ -191,12 +191,12 @@ describe('encryption', () => {
// different key but empty remote => ok // different key but empty remote => ok
handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd00"` }) 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 // rmote is now non empty : can't modify key anymore
await fs.writeFile(`${dir}/nonempty.json`, 'content') await fs.writeFile(`${dir}/nonempty.json`, 'content')
handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd10"` }) 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 () => { it('sync should fail when changing algorithm', async () => {
@ -210,6 +210,6 @@ describe('encryption', () => {
await fs.writeFile(`${dir}/nonempty.json`, 'content') await fs.writeFile(`${dir}/nonempty.json`, 'content')
handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd91"` }) handler = getHandler({ url: `file://${dir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd91"` })
await expect(handler._checkMetadata()).rejects.toThrowError() await expect(handler.sync()).rejects.toThrowError()
}) })
}) })

View File

@ -36,9 +36,11 @@
- @vates/otp major - @vates/otp major
- @vates/predicates minor - @vates/predicates minor
- @vates/read-chunk patch - @vates/read-chunk patch
- @xen-orchestra/backups patch
- @xen-orchestra/fs minor - @xen-orchestra/fs minor
- @xen-orchestra/log minor - @xen-orchestra/log minor
- @xen-orchestra/mixins patch - vhd-cli patch
- vhd-lib patch
- xo-remote-parser patch - xo-remote-parser patch
- xo-server minor - xo-server minor
- xo-server-transport-nagios patch - xo-server-transport-nagios patch

View File

@ -5,22 +5,28 @@
const rimraf = require('rimraf') const rimraf = require('rimraf')
const tmp = require('tmp') const tmp = require('tmp')
const fs = require('fs-extra') 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 { Disposable, pFromCallback } = require('promise-toolbox')
const { openVhd, VhdDirectory } = require('../') const { openVhd, VhdDirectory } = require('../')
const { createRandomFile, convertFromRawToVhd, convertToVhdDirectory } = require('../tests/utils') const { createRandomFile, convertFromRawToVhd, convertToVhdDirectory } = require('../tests/utils')
let tempDir = null let tempDir = null
let handler
let disposeHandler
jest.setTimeout(60000) jest.setTimeout(60000)
beforeEach(async () => { beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb)) tempDir = await pFromCallback(cb => tmp.dir(cb))
const d = await getSyncedHandler({ url: `file://${tempDir}` })
handler = d.value
disposeHandler = d.dispose
}) })
afterEach(async () => { afterEach(async () => {
await pFromCallback(cb => rimraf(tempDir, cb)) await pFromCallback(cb => rimraf(tempDir, cb))
disposeHandler()
}) })
test('Can coalesce block', async () => { test('Can coalesce block', async () => {
@ -45,12 +51,11 @@ test('Can coalesce block', async () => {
await convertToVhdDirectory(childRawDirectoryName, childDirectoryFileName, childDirectoryName) await convertToVhdDirectory(childRawDirectoryName, childDirectoryFileName, childDirectoryName)
await Disposable.use(async function* () { await Disposable.use(async function* () {
const handler = getHandler({ url: 'file://' }) const parentVhd = yield openVhd(handler, 'parent.dir.vhd', { flags: 'w' })
const parentVhd = yield openVhd(handler, parentDirectoryName, { flags: 'w' })
await parentVhd.readBlockAllocationTable() await parentVhd.readBlockAllocationTable()
const childFileVhd = yield openVhd(handler, childFileName) const childFileVhd = yield openVhd(handler, 'childFile.vhd')
await childFileVhd.readBlockAllocationTable() await childFileVhd.readBlockAllocationTable()
const childDirectoryVhd = yield openVhd(handler, childDirectoryName) const childDirectoryVhd = yield openVhd(handler, 'childDir.vhd')
await childDirectoryVhd.readBlockAllocationTable() await childDirectoryVhd.readBlockAllocationTable()
let childBlockData = (await childDirectoryVhd.readBlock(0)).data let childBlockData = (await childDirectoryVhd.readBlock(0)).data
@ -83,7 +88,6 @@ test('compressed blocks and metadata works', async () => {
await createRandomFile(rawFileName, initalSize) await createRandomFile(rawFileName, initalSize)
await convertFromRawToVhd(rawFileName, vhdName) await convertFromRawToVhd(rawFileName, vhdName)
await Disposable.use(async function* () { await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
const vhd = yield openVhd(handler, 'parent.vhd') const vhd = yield openVhd(handler, 'parent.vhd')
await vhd.readBlockAllocationTable() await vhd.readBlockAllocationTable()
const compressedVhd = yield VhdDirectory.create(handler, 'compressed.vhd', { compression: 'gzip' }) const compressedVhd = yield VhdDirectory.create(handler, 'compressed.vhd', { compression: 'gzip' })

View File

@ -7,7 +7,7 @@ const fs = require('fs-extra')
const getStream = require('get-stream') const getStream = require('get-stream')
const rimraf = require('rimraf') const rimraf = require('rimraf')
const tmp = require('tmp') const tmp = require('tmp')
const { getHandler } = require('@xen-orchestra/fs') const { getSyncedHandler } = require('@xen-orchestra/fs')
const { Disposable, pFromCallback } = require('promise-toolbox') const { Disposable, pFromCallback } = require('promise-toolbox')
const { randomBytes } = require('crypto') const { randomBytes } = require('crypto')
@ -24,15 +24,22 @@ const {
} = require('../tests/utils') } = require('../tests/utils')
let tempDir = null let tempDir = null
let handler
let disposeHandler
jest.setTimeout(60000) jest.setTimeout(60000)
beforeEach(async () => { beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb)) tempDir = await pFromCallback(cb => tmp.dir(cb))
const d = await getSyncedHandler({ url: `file://${tempDir}` })
handler = d.value
disposeHandler = d.dispose
}) })
afterEach(async () => { afterEach(async () => {
await pFromCallback(cb => rimraf(tempDir, cb)) await pFromCallback(cb => rimraf(tempDir, cb))
disposeHandler()
}) })
test('respect the checkSecondFooter flag', async () => { test('respect the checkSecondFooter flag', async () => {
@ -42,8 +49,6 @@ test('respect the checkSecondFooter flag', async () => {
const vhdFileName = `${tempDir}/randomfile.vhd` const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName) await convertFromRawToVhd(rawFileName, vhdFileName)
const handler = getHandler({ url: `file://${tempDir}` })
const size = await handler.getSize('randomfile.vhd') const size = await handler.getSize('randomfile.vhd')
const fd = await handler.openFile('randomfile.vhd', 'r+') const fd = await handler.openFile('randomfile.vhd', 'r+')
const buffer = Buffer.alloc(512, 0) const buffer = Buffer.alloc(512, 0)
@ -64,9 +69,8 @@ test('blocks can be moved', async () => {
await createRandomFile(rawFileName, initalSize) await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/randomfile.vhd` const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName) await convertFromRawToVhd(rawFileName, vhdFileName)
const handler = getHandler({ url: 'file://' }) const originalSize = await handler.getSize('randomfile')
const originalSize = await handler.getSize(rawFileName) const newVhd = new VhdFile(handler, 'randomfile.vhd')
const newVhd = new VhdFile(handler, vhdFileName)
await newVhd.readHeaderAndFooter() await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable() await newVhd.readBlockAllocationTable()
await newVhd._freeFirstBlockSpace(8000000) 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 randomBuffer = await pFromCallback(cb => randomBytes(SECTOR_SIZE, cb))
const emptyFileName = `${tempDir}/empty.vhd` const emptyFileName = `${tempDir}/empty.vhd`
await execa('qemu-img', ['create', '-fvpc', emptyFileName, '1.8T']) await execa('qemu-img', ['create', '-fvpc', emptyFileName, '1.8T'])
const handler = getHandler({ url: 'file://' }) const vhd = new VhdFile(handler, 'empty.vhd')
const vhd = new VhdFile(handler, emptyFileName)
await vhd.readHeaderAndFooter() await vhd.readHeaderAndFooter()
await vhd.readBlockAllocationTable() await vhd.readBlockAllocationTable()
// we want the bit 31 to be on, to prove it's not been used for sign // 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 recoveredFileName = `${tempDir}/recovered`
const recoveredFile = await fs.open(recoveredFileName, 'w') const recoveredFile = await fs.open(recoveredFileName, 'w')
try { try {
const vhd2 = new VhdFile(handler, emptyFileName) const vhd2 = new VhdFile(handler, 'empty.vhd')
await vhd2.readHeaderAndFooter() await vhd2.readHeaderAndFooter()
await vhd2.readBlockAllocationTable() await vhd2.readBlockAllocationTable()
for (let i = 0; i < vhd.header.maxTableEntries; i++) { for (let i = 0; i < vhd.header.maxTableEntries; i++) {
@ -126,9 +129,8 @@ test('writeData on empty file', async () => {
await createRandomFile(rawFileName, mbOfRandom) await createRandomFile(rawFileName, mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M']) await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M'])
const randomData = await fs.readFile(rawFileName) const randomData = await fs.readFile(rawFileName)
const handler = getHandler({ url: 'file://' }) const originalSize = await handler.getSize('randomfile')
const originalSize = await handler.getSize(rawFileName) const newVhd = new VhdFile(handler, 'empty.vhd')
const newVhd = new VhdFile(handler, emptyFileName)
await newVhd.readHeaderAndFooter() await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable() await newVhd.readBlockAllocationTable()
await newVhd.writeData(0, randomData) await newVhd.writeData(0, randomData)
@ -145,9 +147,8 @@ test('writeData in 2 non-overlaping operations', async () => {
await createRandomFile(rawFileName, mbOfRandom) await createRandomFile(rawFileName, mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M']) await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M'])
const randomData = await fs.readFile(rawFileName) const randomData = await fs.readFile(rawFileName)
const handler = getHandler({ url: 'file://' }) const originalSize = await handler.getSize('randomfile')
const originalSize = await handler.getSize(rawFileName) const newVhd = new VhdFile(handler, 'empty.vhd')
const newVhd = new VhdFile(handler, emptyFileName)
await newVhd.readHeaderAndFooter() await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable() await newVhd.readBlockAllocationTable()
const splitPointSectors = 2 const splitPointSectors = 2
@ -165,9 +166,8 @@ test('writeData in 2 overlaping operations', async () => {
await createRandomFile(rawFileName, mbOfRandom) await createRandomFile(rawFileName, mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M']) await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M'])
const randomData = await fs.readFile(rawFileName) const randomData = await fs.readFile(rawFileName)
const handler = getHandler({ url: 'file://' }) const originalSize = await handler.getSize('randomfile')
const originalSize = await handler.getSize(rawFileName) const newVhd = new VhdFile(handler, 'empty.vhd')
const newVhd = new VhdFile(handler, emptyFileName)
await newVhd.readHeaderAndFooter() await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable() await newVhd.readBlockAllocationTable()
const endFirstWrite = 3 const endFirstWrite = 3
@ -185,9 +185,8 @@ test('BAT can be extended and blocks moved', async () => {
const vhdFileName = `${tempDir}/randomfile.vhd` const vhdFileName = `${tempDir}/randomfile.vhd`
await createRandomFile(rawFileName, initalSize) await createRandomFile(rawFileName, initalSize)
await convertFromRawToVhd(rawFileName, vhdFileName) await convertFromRawToVhd(rawFileName, vhdFileName)
const handler = getHandler({ url: 'file://' }) const originalSize = await handler.getSize('randomfile')
const originalSize = await handler.getSize(rawFileName) const newVhd = new VhdFile(handler, 'randomfile.vhd')
const newVhd = new VhdFile(handler, vhdFileName)
await newVhd.readHeaderAndFooter() await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable() await newVhd.readBlockAllocationTable()
await newVhd.ensureBatSize(2000) await newVhd.ensureBatSize(2000)
@ -214,12 +213,11 @@ test('Can coalesce block', async () => {
await convertToVhdDirectory(childRawDirectoryName, childDirectoryFileName, childDirectoryName) await convertToVhdDirectory(childRawDirectoryName, childDirectoryFileName, childDirectoryName)
await Disposable.use(async function* () { await Disposable.use(async function* () {
const handler = getHandler({ url: 'file://' }) const parentVhd = yield openVhd(handler, 'parent.vhd', { flags: 'r+' })
const parentVhd = yield openVhd(handler, parentFileName, { flags: 'r+' })
await parentVhd.readBlockAllocationTable() await parentVhd.readBlockAllocationTable()
const childFileVhd = yield openVhd(handler, childFileName) const childFileVhd = yield openVhd(handler, 'childFile.vhd')
await childFileVhd.readBlockAllocationTable() await childFileVhd.readBlockAllocationTable()
const childDirectoryVhd = yield openVhd(handler, childDirectoryName) const childDirectoryVhd = yield openVhd(handler, 'childDir.vhd')
await childDirectoryVhd.readBlockAllocationTable() await childDirectoryVhd.readBlockAllocationTable()
await parentVhd.mergeBlock(childFileVhd, 0) await parentVhd.mergeBlock(childFileVhd, 0)

View File

@ -5,7 +5,7 @@
const fs = require('fs-extra') const fs = require('fs-extra')
const rimraf = require('rimraf') const rimraf = require('rimraf')
const tmp = require('tmp') const tmp = require('tmp')
const { getHandler } = require('@xen-orchestra/fs') const { getSyncedHandler } = require('@xen-orchestra/fs')
const { pFromCallback } = require('promise-toolbox') const { pFromCallback } = require('promise-toolbox')
const { VhdFile, chainVhd } = require('./index') const { VhdFile, chainVhd } = require('./index')
@ -14,15 +14,21 @@ const { _cleanupVhds: cleanupVhds, mergeVhdChain } = require('./merge')
const { checkFile, createRandomFile, convertFromRawToVhd } = require('./tests/utils') const { checkFile, createRandomFile, convertFromRawToVhd } = require('./tests/utils')
let tempDir = null let tempDir = null
let handler
let disposeHandler
jest.setTimeout(60000) jest.setTimeout(60000)
beforeEach(async () => { beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb)) tempDir = await pFromCallback(cb => tmp.dir(cb))
const d = await getSyncedHandler({ url: `file://${tempDir}` })
handler = d.value
disposeHandler = d.dispose
}) })
afterEach(async () => { afterEach(async () => {
await pFromCallback(cb => rimraf(tempDir, cb)) await pFromCallback(cb => rimraf(tempDir, cb))
disposeHandler()
}) })
test('merge works in normal cases', async () => { test('merge works in normal cases', async () => {
@ -32,7 +38,6 @@ test('merge works in normal cases', async () => {
const childRandomFileName = `small_randomfile` const childRandomFileName = `small_randomfile`
const parentFileName = `parent.vhd` const parentFileName = `parent.vhd`
const child1FileName = `child1.vhd` const child1FileName = `child1.vhd`
const handler = getHandler({ url: `file://${tempDir}` })
await createRandomFile(`${tempDir}/${parentRandomFileName}`, mbOfFather) await createRandomFile(`${tempDir}/${parentRandomFileName}`, mbOfFather)
await convertFromRawToVhd(`${tempDir}/${parentRandomFileName}`, `${tempDir}/${parentFileName}`) await convertFromRawToVhd(`${tempDir}/${parentRandomFileName}`, `${tempDir}/${parentFileName}`)
@ -70,7 +75,6 @@ test('it can resume a simple merge ', async () => {
const mbOfChildren = 4 const mbOfChildren = 4
const parentRandomFileName = `${tempDir}/randomfile` const parentRandomFileName = `${tempDir}/randomfile`
const childRandomFileName = `${tempDir}/small_randomfile` const childRandomFileName = `${tempDir}/small_randomfile`
const handler = getHandler({ url: `file://${tempDir}` })
await createRandomFile(`${tempDir}/randomfile`, mbOfFather) await createRandomFile(`${tempDir}/randomfile`, mbOfFather)
await convertFromRawToVhd(`${tempDir}/randomfile`, `${tempDir}/parent.vhd`) 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 parentFileName = `${tempDir}/parent.vhd`
const childFileName = `${tempDir}/child.vhd` const childFileName = `${tempDir}/child.vhd`
const grandChildFileName = `${tempDir}/grandchild.vhd` const grandChildFileName = `${tempDir}/grandchild.vhd`
const handler = getHandler({ url: 'file://' })
await createRandomFile(parentRandomFileName, mbOfFather) await createRandomFile(parentRandomFileName, mbOfFather)
await convertFromRawToVhd(parentRandomFileName, parentFileName) await convertFromRawToVhd(parentRandomFileName, parentFileName)
await createRandomFile(childRandomFileName, mbOfChildren) await createRandomFile(childRandomFileName, mbOfChildren)
await convertFromRawToVhd(childRandomFileName, childFileName) 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 createRandomFile(grandChildRandomFileName, mbOfGrandChildren)
await convertFromRawToVhd(grandChildRandomFileName, grandChildFileName) 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() await parentVhd.readHeaderAndFooter()
const childVhd = new VhdFile(handler, childFileName) const childVhd = new VhdFile(handler, 'child.vhd')
await childVhd.readHeaderAndFooter() await childVhd.readHeaderAndFooter()
const grandChildVhd = new VhdFile(handler, grandChildFileName) const grandChildVhd = new VhdFile(handler, 'grandchild.vhd')
await grandChildVhd.readHeaderAndFooter() await grandChildVhd.readHeaderAndFooter()
await handler.writeFile( await handler.writeFile(
`${tempDir}/.parent.vhd.merge.json`, `.parent.vhd.merge.json`,
JSON.stringify({ JSON.stringify({
parent: { parent: {
header: parentVhd.header.checksum, 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 // should fail since the merge state file has only data of parent and child
await expect( await expect(
async () => await mergeVhdChain(handler, [parentFileName, childFileName, grandChildFileName]) async () => await mergeVhdChain(handler, ['parent.vhd', 'child.vhd', 'grandchild.vhd'])
).rejects.toThrow() ).rejects.toThrow()
// merge // merge
await handler.unlink(`${tempDir}/.parent.vhd.merge.json`) await handler.unlink(`.parent.vhd.merge.json`)
await handler.writeFile( await handler.writeFile(
`${tempDir}/.parent.vhd.merge.json`, `.parent.vhd.merge.json`,
JSON.stringify({ JSON.stringify({
parent: { parent: {
header: parentVhd.header.checksum, header: parentVhd.header.checksum,
@ -219,11 +222,11 @@ test('it can resume a multiple merge ', async () => {
header: grandChildVhd.header.checksum, header: grandChildVhd.header.checksum,
}, },
currentBlock: 1, currentBlock: 1,
childPath: [childVhd, grandChildVhd], childPath: ['child.vhd', 'grandchild.vhd'],
}) })
) )
// it should succeed // 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 () => { 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 parentFileName = `${tempDir}/parent.vhd`
const childFileName = `${tempDir}/child.vhd` const childFileName = `${tempDir}/child.vhd`
const grandChildFileName = `${tempDir}/grandchild.vhd` const grandChildFileName = `${tempDir}/grandchild.vhd`
const handler = getHandler({ url: 'file://' })
await createRandomFile(parentRandomFileName, mbOfFather) await createRandomFile(parentRandomFileName, mbOfFather)
await convertFromRawToVhd(parentRandomFileName, parentFileName) await convertFromRawToVhd(parentRandomFileName, parentFileName)
await createRandomFile(childRandomFileName, mbOfChildren) await createRandomFile(childRandomFileName, mbOfChildren)
await convertFromRawToVhd(childRandomFileName, childFileName) 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 createRandomFile(grandChildRandomFileName, mbOfGrandChildren)
await convertFromRawToVhd(grandChildRandomFileName, grandChildFileName) await convertFromRawToVhd(grandChildRandomFileName, grandChildFileName)
await chainVhd(handler, childFileName, handler, grandChildFileName, true) await chainVhd(handler, 'child.vhd', handler, 'grandchild.vhd', true)
// merge // merge
await mergeVhdChain(handler, [parentFileName, childFileName, grandChildFileName]) await mergeVhdChain(handler, ['parent.vhd', 'child.vhd', 'grandchild.vhd'])
// check that vhd is still valid // check that vhd is still valid
await checkFile(grandChildFileName) await checkFile(grandChildFileName)
const parentVhd = new VhdFile(handler, grandChildFileName) const parentVhd = new VhdFile(handler, 'grandchild.vhd')
await parentVhd.readHeaderAndFooter() await parentVhd.readHeaderAndFooter()
await parentVhd.readBlockAllocationTable() await parentVhd.readBlockAllocationTable()
@ -277,8 +280,6 @@ test('it merge multiple child in one pass ', async () => {
}) })
test('it cleans vhd mergedfiles', async () => { test('it cleans vhd mergedfiles', async () => {
const handler = getHandler({ url: `file://${tempDir}` })
await handler.writeFile('parent', 'parentData') await handler.writeFile('parent', 'parentData')
await handler.writeFile('child1', 'child1Data') await handler.writeFile('child1', 'child1Data')
await handler.writeFile('child2', 'child2Data') await handler.writeFile('child2', 'child2Data')

View File

@ -4,6 +4,7 @@
const rimraf = require('rimraf') const rimraf = require('rimraf')
const tmp = require('tmp') const tmp = require('tmp')
const fs = require('node:fs/promises')
const { getSyncedHandler } = require('@xen-orchestra/fs') const { getSyncedHandler } = require('@xen-orchestra/fs')
const { Disposable, pFromCallback } = require('promise-toolbox') 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` const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName) await convertFromRawToVhd(rawFileName, vhdFileName)
await Disposable.use(async function* () { await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file://' }) const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
const vhd = yield openVhd(handler, vhdFileName) const vhd = yield openVhd(handler, 'randomfile.vhd')
expect(vhd.header.cookie).toEqual('cxsparse') expect(vhd.header.cookie).toEqual('cxsparse')
expect(vhd.footer.cookie).toEqual('conectix') expect(vhd.footer.cookie).toEqual('conectix')
const aliasFileName = `${tempDir}/out.alias.vhd` const aliasFileName = `out.alias.vhd`
await VhdAbstract.createAlias(handler, aliasFileName, vhdFileName) await VhdAbstract.createAlias(handler, aliasFileName, 'randomfile.vhd')
const alias = yield openVhd(handler, aliasFileName) const alias = yield openVhd(handler, aliasFileName)
expect(alias.header.cookie).toEqual('cxsparse') expect(alias.header.cookie).toEqual('cxsparse')
expect(alias.footer.cookie).toEqual('conectix') expect(alias.footer.cookie).toEqual('conectix')
@ -50,15 +51,77 @@ test('It opens a vhd directory', async () => {
await createRandomVhdDirectory(vhdDirectory, initalSize) await createRandomVhdDirectory(vhdDirectory, initalSize)
await Disposable.use(async function* () { await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file://' }) const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
const vhd = yield openVhd(handler, vhdDirectory) const vhd = yield openVhd(handler, 'randomfile.dir')
expect(vhd.header.cookie).toEqual('cxsparse') expect(vhd.header.cookie).toEqual('cxsparse')
expect(vhd.footer.cookie).toEqual('conectix') expect(vhd.footer.cookie).toEqual('conectix')
const aliasFileName = `${tempDir}/out.alias.vhd` const aliasFileName = `out.alias.vhd`
await VhdAbstract.createAlias(handler, aliasFileName, vhdDirectory) await VhdAbstract.createAlias(handler, aliasFileName, 'randomfile.dir')
const alias = yield openVhd(handler, aliasFileName) const alias = yield openVhd(handler, aliasFileName)
expect(alias.header.cookie).toEqual('cxsparse') expect(alias.header.cookie).toEqual('cxsparse')
expect(alias.footer.cookie).toEqual('conectix') 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()
})