test(vhd-lib): from Jest to test

This commit is contained in:
Julien Fontanet
2023-06-01 16:53:32 +02:00
parent e1145f35ee
commit ea34516d73
18 changed files with 1322 additions and 1300 deletions

View File

@@ -0,0 +1,273 @@
'use strict'
const { beforeEach, afterEach, describe, it } = require('test')
const { strict: assert } = require('assert')
const { rimraf } = require('rimraf')
const tmp = require('tmp')
const fs = require('fs-extra')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { Disposable, pFromCallback } = require('promise-toolbox')
const { openVhd } = require('../index')
const { checkFile, createRandomFile, convertFromRawToVhd, createRandomVhdDirectory } = require('../tests/utils')
const { VhdAbstract } = require('./VhdAbstract')
const { BLOCK_UNUSED, FOOTER_SIZE, HEADER_SIZE, PLATFORMS, SECTOR_SIZE } = require('../_constants')
const { unpackHeader, unpackFooter } = require('./_utils')
const streamToBuffer = stream => {
let buffer = Buffer.alloc(0)
return new Promise((resolve, reject) => {
stream.on('data', data => (buffer = Buffer.concat([buffer, data])))
stream.on('end', () => resolve(buffer))
})
}
let tempDir
describe('VhdAbstract', async () => {
beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb))
})
afterEach(async () => {
await rimraf(tempDir)
})
it('Creates an alias', async () => {
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file://' + tempDir })
const aliasPath = `alias/alias.alias.vhd`
const aliasFsPath = `${tempDir}/${aliasPath}`
await fs.mkdirp(`${tempDir}/alias`)
const testOneCombination = async ({ targetPath, targetContent }) => {
await VhdAbstract.createAlias(handler, aliasPath, targetPath)
// alias file is created
assert.equal(await fs.exists(aliasFsPath), true)
// content is the target path relative to the alias location
const content = await fs.readFile(aliasFsPath, 'utf-8')
assert.equal(content, targetContent)
// create alias fails if alias already exists, remove it before next loop step
await fs.unlink(aliasFsPath)
}
const combinations = [
{ targetPath: `targets.vhd`, targetContent: `../targets.vhd` },
{ targetPath: `alias/targets.vhd`, targetContent: `targets.vhd` },
{ targetPath: `alias/sub/targets.vhd`, targetContent: `sub/targets.vhd` },
{ targetPath: `sibling/targets.vhd`, targetContent: `../sibling/targets.vhd` },
]
for (const { targetPath, targetContent } of combinations) {
await testOneCombination({ targetPath, targetContent })
}
})
})
it('has *.alias.vhd extension', async () => {
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file:///' })
const aliasPath = `${tempDir}/invalidalias.vhd`
const targetPath = `${tempDir}/targets.vhd`
await assert.rejects(async () => await VhdAbstract.createAlias(handler, aliasPath, targetPath))
assert.equal(await fs.exists(aliasPath), false)
})
})
it('must not be chained', async () => {
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file:///' })
const aliasPath = `${tempDir}/valid.alias.vhd`
const targetPath = `${tempDir}/an.other.valid.alias.vhd`
await assert.rejects(async () => await VhdAbstract.createAlias(handler, aliasPath, targetPath))
assert.equal(await fs.exists(aliasPath), false)
})
})
it('renames and unlink a VHDFile', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file:///' })
await VhdAbstract.unlink(handler, vhdFileName)
assert.equal(await fs.exists(vhdFileName), false)
})
})
it('renames and unlink a VhdDirectory', async () => {
const initalSize = 4
const vhdDirectory = `${tempDir}/randomfile.dir`
await createRandomVhdDirectory(vhdDirectory, initalSize)
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file:///' })
const vhd = yield openVhd(handler, vhdDirectory)
assert.equal(vhd.header.cookie, 'cxsparse')
assert.equal(vhd.footer.cookie, 'conectix')
const targetFileName = `${tempDir}/renamed.vhd`
// it should clean an existing directory
await fs.mkdir(targetFileName)
await fs.writeFile(`${targetFileName}/dummy`, 'I exists')
await VhdAbstract.unlink(handler, `${targetFileName}/dummy`)
assert.equal(await fs.exists(`${targetFileName}/dummy`), false)
})
})
it('Creates, renames and unlink alias', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
const aliasFileName = `${tempDir}/aliasFileName.alias.vhd`
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file:///' })
await VhdAbstract.createAlias(handler, aliasFileName, vhdFileName)
assert.equal(await fs.exists(aliasFileName), true)
assert.equal(await fs.exists(vhdFileName), true)
await VhdAbstract.unlink(handler, aliasFileName)
assert.equal(await fs.exists(aliasFileName), false)
assert.equal(await fs.exists(vhdFileName), false)
})
})
it('creates a vhd stream', async () => {
const initialNbBlocks = 3
const initalSize = initialNbBlocks * 2
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/vhd.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file://' + tempDir })
const vhd = yield openVhd(handler, 'vhd.vhd')
await vhd.readBlockAllocationTable()
const parentLocatorBase = Buffer.from('a file path, not aligned', 'utf16le')
const aligned = Buffer.alloc(SECTOR_SIZE, 0)
parentLocatorBase.copy(aligned)
await vhd.writeParentLocator({
id: 0,
platformCode: PLATFORMS.W2KU,
data: parentLocatorBase,
})
await vhd.writeFooter()
const stream = vhd.stream()
// size and stream must have the same result
assert.equal(stream.length, vhd.streamSize())
assert.equal(
stream.length,
512 /* footer */ +
1024 /* header */ +
512 /* BAT */ +
512 /* parentlocator */ +
3 * (2 * 1024 * 1024 + 512) /* blocs */ +
512 /* end footer */
)
// read all the stream into a buffer
const buffer = await streamToBuffer(stream)
const length = buffer.length
const bufFooter = buffer.slice(0, FOOTER_SIZE)
// footer is still valid
assert.doesNotThrow(() => unpackFooter(bufFooter))
const footer = unpackFooter(bufFooter)
// header is still valid
const bufHeader = buffer.slice(FOOTER_SIZE, HEADER_SIZE + FOOTER_SIZE)
assert.doesNotThrow(() => unpackHeader(bufHeader, footer))
// 1 deleted block should be in ouput
let start = FOOTER_SIZE + HEADER_SIZE + vhd.batSize
const parentLocatorData = buffer.slice(start, start + SECTOR_SIZE)
assert.equal(parentLocatorData.equals(aligned), true)
start += SECTOR_SIZE // parent locator
assert.equal(length, start + initialNbBlocks * vhd.fullBlockSize + FOOTER_SIZE)
assert.equal(stream.length, buffer.length)
// blocks
const blockBuf = Buffer.alloc(vhd.sectorsPerBlock * SECTOR_SIZE, 0)
for (let i = 0; i < initialNbBlocks; i++) {
const blockDataStart = start + i * vhd.fullBlockSize + 512 /* block bitmap */
const blockDataEnd = blockDataStart + vhd.sectorsPerBlock * SECTOR_SIZE
const content = buffer.slice(blockDataStart, blockDataEnd)
await handler.read('randomfile', blockBuf, i * vhd.sectorsPerBlock * SECTOR_SIZE)
assert.equal(content.equals(blockBuf), true)
}
// footer
const endFooter = buffer.slice(length - FOOTER_SIZE)
assert.deepEqual(bufFooter, endFooter)
await handler.writeFile('out.vhd', buffer)
// check that the vhd is still valid
await checkFile(`${tempDir}/out.vhd`)
})
})
it('can stream content', async () => {
const initalSizeMb = 5 // 2 block and an half
const initialNbBlocks = Math.ceil(initalSizeMb / 2)
const initialByteSize = initalSizeMb * 1024 * 1024
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSizeMb)
const vhdFileName = `${tempDir}/vhd.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
const bat = Buffer.alloc(512)
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file://' + tempDir })
const vhd = yield openVhd(handler, 'vhd.vhd')
// mark first block as unused
await handler.read('vhd.vhd', bat, vhd.header.tableOffset)
bat.writeUInt32BE(BLOCK_UNUSED, 0)
await handler.write('vhd.vhd', bat, vhd.header.tableOffset)
// read our modified block allocation table
await vhd.readBlockAllocationTable()
const stream = vhd.rawContent()
const buffer = await streamToBuffer(stream)
// qemu can modify size, to align it to geometry
// check that data didn't change
const blockDataLength = vhd.sectorsPerBlock * SECTOR_SIZE
// first block should be empty
const EMPTY = Buffer.alloc(blockDataLength, 0)
const firstBlock = buffer.slice(0, blockDataLength)
// using buffer1 toEquals buffer2 make jest crash trying to stringify it on failure
assert.equal(firstBlock.equals(EMPTY), true)
let remainingLength = initialByteSize - blockDataLength // already checked the first block
for (let i = 1; i < initialNbBlocks; i++) {
// last block will be truncated
const blockSize = Math.min(blockDataLength, remainingLength - blockDataLength)
const blockDataStart = i * blockDataLength // first block have been deleted
const blockDataEnd = blockDataStart + blockSize
const content = buffer.slice(blockDataStart, blockDataEnd)
const blockBuf = Buffer.alloc(blockSize, 0)
await handler.read('randomfile', blockBuf, i * blockDataLength)
assert.equal(content.equals(blockBuf), true)
remainingLength -= blockSize
}
})
})
})

View File

@@ -1,271 +0,0 @@
'use strict'
/* eslint-env jest */
const tmp = require('tmp')
const fs = require('fs-extra')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { Disposable, pFromCallback } = require('promise-toolbox')
const { rimraf } = require('rimraf')
const { openVhd } = require('../index')
const { checkFile, createRandomFile, convertFromRawToVhd, createRandomVhdDirectory } = require('../tests/utils')
const { VhdAbstract } = require('./VhdAbstract')
const { BLOCK_UNUSED, FOOTER_SIZE, HEADER_SIZE, PLATFORMS, SECTOR_SIZE } = require('../_constants')
const { unpackHeader, unpackFooter } = require('./_utils')
let tempDir
jest.setTimeout(60000)
beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb))
})
afterEach(async () => {
await rimraf(tempDir)
})
const streamToBuffer = stream => {
let buffer = Buffer.alloc(0)
return new Promise((resolve, reject) => {
stream.on('data', data => (buffer = Buffer.concat([buffer, data])))
stream.on('end', () => resolve(buffer))
})
}
test('It creates an alias', async () => {
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file://' + tempDir })
const aliasPath = `alias/alias.alias.vhd`
const aliasFsPath = `${tempDir}/${aliasPath}`
await fs.mkdirp(`${tempDir}/alias`)
const testOneCombination = async ({ targetPath, targetContent }) => {
await VhdAbstract.createAlias(handler, aliasPath, targetPath)
// alias file is created
expect(await fs.exists(aliasFsPath)).toEqual(true)
// content is the target path relative to the alias location
const content = await fs.readFile(aliasFsPath, 'utf-8')
expect(content).toEqual(targetContent)
// create alias fails if alias already exists, remove it before next loop step
await fs.unlink(aliasFsPath)
}
const combinations = [
{ targetPath: `targets.vhd`, targetContent: `../targets.vhd` },
{ targetPath: `alias/targets.vhd`, targetContent: `targets.vhd` },
{ targetPath: `alias/sub/targets.vhd`, targetContent: `sub/targets.vhd` },
{ targetPath: `sibling/targets.vhd`, targetContent: `../sibling/targets.vhd` },
]
for (const { targetPath, targetContent } of combinations) {
await testOneCombination({ targetPath, targetContent })
}
})
})
test('alias must have *.alias.vhd extension', async () => {
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file:///' })
const aliasPath = `${tempDir}/invalidalias.vhd`
const targetPath = `${tempDir}/targets.vhd`
expect(async () => await VhdAbstract.createAlias(handler, aliasPath, targetPath)).rejects.toThrow()
expect(await fs.exists(aliasPath)).toEqual(false)
})
})
test('alias must not be chained', async () => {
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file:///' })
const aliasPath = `${tempDir}/valid.alias.vhd`
const targetPath = `${tempDir}/an.other.valid.alias.vhd`
expect(async () => await VhdAbstract.createAlias(handler, aliasPath, targetPath)).rejects.toThrow()
expect(await fs.exists(aliasPath)).toEqual(false)
})
})
test('It rename and unlink a VHDFile', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file:///' })
await VhdAbstract.unlink(handler, vhdFileName)
expect(await fs.exists(vhdFileName)).toEqual(false)
})
})
test('It rename and unlink a VhdDirectory', async () => {
const initalSize = 4
const vhdDirectory = `${tempDir}/randomfile.dir`
await createRandomVhdDirectory(vhdDirectory, initalSize)
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file:///' })
const vhd = yield openVhd(handler, vhdDirectory)
expect(vhd.header.cookie).toEqual('cxsparse')
expect(vhd.footer.cookie).toEqual('conectix')
const targetFileName = `${tempDir}/renamed.vhd`
// it should clean an existing directory
await fs.mkdir(targetFileName)
await fs.writeFile(`${targetFileName}/dummy`, 'I exists')
await VhdAbstract.unlink(handler, `${targetFileName}/dummy`)
expect(await fs.exists(`${targetFileName}/dummy`)).toEqual(false)
})
})
test('It create , rename and unlink alias', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
const aliasFileName = `${tempDir}/aliasFileName.alias.vhd`
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file:///' })
await VhdAbstract.createAlias(handler, aliasFileName, vhdFileName)
expect(await fs.exists(aliasFileName)).toEqual(true)
expect(await fs.exists(vhdFileName)).toEqual(true)
await VhdAbstract.unlink(handler, aliasFileName)
expect(await fs.exists(aliasFileName)).toEqual(false)
expect(await fs.exists(vhdFileName)).toEqual(false)
})
})
test('it can create a vhd stream', async () => {
const initialNbBlocks = 3
const initalSize = initialNbBlocks * 2
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/vhd.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file://' + tempDir })
const vhd = yield openVhd(handler, 'vhd.vhd')
await vhd.readBlockAllocationTable()
const parentLocatorBase = Buffer.from('a file path, not aligned', 'utf16le')
const aligned = Buffer.alloc(SECTOR_SIZE, 0)
parentLocatorBase.copy(aligned)
await vhd.writeParentLocator({
id: 0,
platformCode: PLATFORMS.W2KU,
data: parentLocatorBase,
})
await vhd.writeFooter()
const stream = vhd.stream()
// size and stream must have the same result
expect(stream.length).toEqual(vhd.streamSize())
expect(stream.length).toEqual(
512 /* footer */ +
1024 /* header */ +
512 /* BAT */ +
512 /* parentlocator */ +
3 * (2 * 1024 * 1024 + 512) /* blocs */ +
512 /* end footer */
)
// read all the stream into a buffer
const buffer = await streamToBuffer(stream)
const length = buffer.length
const bufFooter = buffer.slice(0, FOOTER_SIZE)
// footer is still valid
expect(() => unpackFooter(bufFooter)).not.toThrow()
const footer = unpackFooter(bufFooter)
// header is still valid
const bufHeader = buffer.slice(FOOTER_SIZE, HEADER_SIZE + FOOTER_SIZE)
expect(() => unpackHeader(bufHeader, footer)).not.toThrow()
// 1 deleted block should be in ouput
let start = FOOTER_SIZE + HEADER_SIZE + vhd.batSize
const parentLocatorData = buffer.slice(start, start + SECTOR_SIZE)
expect(parentLocatorData.equals(aligned)).toEqual(true)
start += SECTOR_SIZE // parent locator
expect(length).toEqual(start + initialNbBlocks * vhd.fullBlockSize + FOOTER_SIZE)
expect(stream.length).toEqual(buffer.length)
// blocks
const blockBuf = Buffer.alloc(vhd.sectorsPerBlock * SECTOR_SIZE, 0)
for (let i = 0; i < initialNbBlocks; i++) {
const blockDataStart = start + i * vhd.fullBlockSize + 512 /* block bitmap */
const blockDataEnd = blockDataStart + vhd.sectorsPerBlock * SECTOR_SIZE
const content = buffer.slice(blockDataStart, blockDataEnd)
await handler.read('randomfile', blockBuf, i * vhd.sectorsPerBlock * SECTOR_SIZE)
expect(content.equals(blockBuf)).toEqual(true)
}
// footer
const endFooter = buffer.slice(length - FOOTER_SIZE)
expect(bufFooter).toEqual(endFooter)
await handler.writeFile('out.vhd', buffer)
// check that the vhd is still valid
await checkFile(`${tempDir}/out.vhd`)
})
})
it('can stream content', async () => {
const initalSizeMb = 5 // 2 block and an half
const initialNbBlocks = Math.ceil(initalSizeMb / 2)
const initialByteSize = initalSizeMb * 1024 * 1024
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSizeMb)
const vhdFileName = `${tempDir}/vhd.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
const bat = Buffer.alloc(512)
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: 'file://' + tempDir })
const vhd = yield openVhd(handler, 'vhd.vhd')
// mark first block as unused
await handler.read('vhd.vhd', bat, vhd.header.tableOffset)
bat.writeUInt32BE(BLOCK_UNUSED, 0)
await handler.write('vhd.vhd', bat, vhd.header.tableOffset)
// read our modified block allocation table
await vhd.readBlockAllocationTable()
const stream = vhd.rawContent()
const buffer = await streamToBuffer(stream)
// qemu can modify size, to align it to geometry
// check that data didn't change
const blockDataLength = vhd.sectorsPerBlock * SECTOR_SIZE
// first block should be empty
const EMPTY = Buffer.alloc(blockDataLength, 0)
const firstBlock = buffer.slice(0, blockDataLength)
// using buffer1 toEquals buffer2 make jest crash trying to stringify it on failure
expect(firstBlock.equals(EMPTY)).toEqual(true)
let remainingLength = initialByteSize - blockDataLength // already checked the first block
for (let i = 1; i < initialNbBlocks; i++) {
// last block will be truncated
const blockSize = Math.min(blockDataLength, remainingLength - blockDataLength)
const blockDataStart = i * blockDataLength // first block have been deleted
const blockDataEnd = blockDataStart + blockSize
const content = buffer.slice(blockDataStart, blockDataEnd)
const blockBuf = Buffer.alloc(blockSize, 0)
await handler.read('randomfile', blockBuf, i * blockDataLength)
expect(content.equals(blockBuf)).toEqual(true)
remainingLength -= blockSize
}
})
})

View File

@@ -0,0 +1,126 @@
'use strict'
const { beforeEach, afterEach, describe, it } = require('test')
const { strict: assert } = require('assert')
const { rimraf } = require('rimraf')
const tmp = require('tmp')
const fs = require('fs-extra')
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
describe('VhdDirectory', async () => {
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 rimraf(tempDir)
disposeHandler()
})
it('Can coalesce block', async () => {
const initalSize = 4
const parentrawFileName = `${tempDir}/randomfile`
const parentFileName = `${tempDir}/parent.vhd`
const parentDirectoryName = `${tempDir}/parent.dir.vhd`
await createRandomFile(parentrawFileName, initalSize)
await convertFromRawToVhd(parentrawFileName, parentFileName)
await convertToVhdDirectory(parentrawFileName, parentFileName, parentDirectoryName)
const childrawFileName = `${tempDir}/randomfile`
const childFileName = `${tempDir}/childFile.vhd`
await createRandomFile(childrawFileName, initalSize)
await convertFromRawToVhd(childrawFileName, childFileName)
const childRawDirectoryName = `${tempDir}/randomFile2.vhd`
const childDirectoryFileName = `${tempDir}/childDirFile.vhd`
const childDirectoryName = `${tempDir}/childDir.vhd`
await createRandomFile(childRawDirectoryName, initalSize)
await convertFromRawToVhd(childRawDirectoryName, childDirectoryFileName)
await convertToVhdDirectory(childRawDirectoryName, childDirectoryFileName, childDirectoryName)
await Disposable.use(async function* () {
const parentVhd = yield openVhd(handler, 'parent.dir.vhd', { flags: 'w' })
await parentVhd.readBlockAllocationTable()
const childFileVhd = yield openVhd(handler, 'childFile.vhd')
await childFileVhd.readBlockAllocationTable()
const childDirectoryVhd = yield openVhd(handler, 'childDir.vhd')
await childDirectoryVhd.readBlockAllocationTable()
let childBlockData = (await childDirectoryVhd.readBlock(0)).data
await parentVhd.mergeBlock(childDirectoryVhd, 0)
await parentVhd.writeFooter()
await parentVhd.writeBlockAllocationTable()
let parentBlockData = (await parentVhd.readBlock(0)).data
// block should be present in parent
assert.equal(parentBlockData.equals(childBlockData), true)
// block should not be in child since it's a rename for vhd directory
await assert.rejects(childDirectoryVhd.readBlock(0))
childBlockData = (await childFileVhd.readBlock(1)).data
await parentVhd.mergeBlock(childFileVhd, 1)
await parentVhd.writeFooter()
await parentVhd.writeBlockAllocationTable()
parentBlockData = (await parentVhd.readBlock(1)).data
// block should be present in parent in case of mixed vhdfile/vhddirectory
assert.equal(parentBlockData.equals(childBlockData), true)
// block should still be child
await childFileVhd.readBlock(1)
})
})
it('compresses blocks and metadata works', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile`
const vhdName = `${tempDir}/parent.vhd`
await createRandomFile(rawFileName, initalSize)
await convertFromRawToVhd(rawFileName, vhdName)
await Disposable.use(async function* () {
const vhd = yield openVhd(handler, 'parent.vhd')
await vhd.readBlockAllocationTable()
const compressedVhd = yield VhdDirectory.create(handler, 'compressed.vhd', { compression: 'gzip' })
compressedVhd.header = vhd.header
compressedVhd.footer = vhd.footer
for await (const block of vhd.blocks()) {
await compressedVhd.writeEntireBlock(block)
}
await Promise
.all[(await compressedVhd.writeHeader(), await compressedVhd.writeFooter(), await compressedVhd.writeBlockAllocationTable())]
// compressed vhd have a metadata file
assert.equal(await fs.exists(`${tempDir}/compressed.vhd/chunk-filters.json`), true)
const metada = JSON.parse(await handler.readFile('compressed.vhd/chunk-filters.json'))
assert.equal(metada[0], 'gzip')
// compressed vhd should not be broken
await compressedVhd.readHeaderAndFooter()
await compressedVhd.readBlockAllocationTable()
// check that footer and header are not modified
assert.deepEqual(compressedVhd.footer, vhd.footer)
assert.deepEqual(compressedVhd.header, vhd.header)
// their block content should not have changed
let counter = 0
for await (const block of compressedVhd.blocks()) {
const source = await vhd.readBlock(block.id)
assert.equal(source.data.equals(block.data), true)
counter++
}
// neither the number of blocks
assert.equal(counter, 2)
})
})
})

View File

@@ -1,125 +0,0 @@
'use strict'
/* eslint-env jest */
const tmp = require('tmp')
const fs = require('fs-extra')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { Disposable, pFromCallback } = require('promise-toolbox')
const { rimraf } = require('rimraf')
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 rimraf(tempDir)
disposeHandler()
})
test('Can coalesce block', async () => {
const initalSize = 4
const parentrawFileName = `${tempDir}/randomfile`
const parentFileName = `${tempDir}/parent.vhd`
const parentDirectoryName = `${tempDir}/parent.dir.vhd`
await createRandomFile(parentrawFileName, initalSize)
await convertFromRawToVhd(parentrawFileName, parentFileName)
await convertToVhdDirectory(parentrawFileName, parentFileName, parentDirectoryName)
const childrawFileName = `${tempDir}/randomfile`
const childFileName = `${tempDir}/childFile.vhd`
await createRandomFile(childrawFileName, initalSize)
await convertFromRawToVhd(childrawFileName, childFileName)
const childRawDirectoryName = `${tempDir}/randomFile2.vhd`
const childDirectoryFileName = `${tempDir}/childDirFile.vhd`
const childDirectoryName = `${tempDir}/childDir.vhd`
await createRandomFile(childRawDirectoryName, initalSize)
await convertFromRawToVhd(childRawDirectoryName, childDirectoryFileName)
await convertToVhdDirectory(childRawDirectoryName, childDirectoryFileName, childDirectoryName)
await Disposable.use(async function* () {
const parentVhd = yield openVhd(handler, 'parent.dir.vhd', { flags: 'w' })
await parentVhd.readBlockAllocationTable()
const childFileVhd = yield openVhd(handler, 'childFile.vhd')
await childFileVhd.readBlockAllocationTable()
const childDirectoryVhd = yield openVhd(handler, 'childDir.vhd')
await childDirectoryVhd.readBlockAllocationTable()
let childBlockData = (await childDirectoryVhd.readBlock(0)).data
await parentVhd.mergeBlock(childDirectoryVhd, 0)
await parentVhd.writeFooter()
await parentVhd.writeBlockAllocationTable()
let parentBlockData = (await parentVhd.readBlock(0)).data
// block should be present in parent
expect(parentBlockData.equals(childBlockData)).toEqual(true)
// block should not be in child since it's a rename for vhd directory
await expect(childDirectoryVhd.readBlock(0)).rejects.toThrowError()
childBlockData = (await childFileVhd.readBlock(1)).data
await parentVhd.mergeBlock(childFileVhd, 1)
await parentVhd.writeFooter()
await parentVhd.writeBlockAllocationTable()
parentBlockData = (await parentVhd.readBlock(1)).data
// block should be present in parent in case of mixed vhdfile/vhddirectory
expect(parentBlockData.equals(childBlockData)).toEqual(true)
// block should still be child
await childFileVhd.readBlock(1)
})
})
test('compressed blocks and metadata works', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile`
const vhdName = `${tempDir}/parent.vhd`
await createRandomFile(rawFileName, initalSize)
await convertFromRawToVhd(rawFileName, vhdName)
await Disposable.use(async function* () {
const vhd = yield openVhd(handler, 'parent.vhd')
await vhd.readBlockAllocationTable()
const compressedVhd = yield VhdDirectory.create(handler, 'compressed.vhd', { compression: 'gzip' })
compressedVhd.header = vhd.header
compressedVhd.footer = vhd.footer
for await (const block of vhd.blocks()) {
await compressedVhd.writeEntireBlock(block)
}
await Promise
.all[(await compressedVhd.writeHeader(), await compressedVhd.writeFooter(), await compressedVhd.writeBlockAllocationTable())]
// compressed vhd have a metadata file
expect(await fs.exists(`${tempDir}/compressed.vhd/chunk-filters.json`)).toEqual(true)
const metada = JSON.parse(await handler.readFile('compressed.vhd/chunk-filters.json'))
expect(metada[0]).toEqual('gzip')
// compressed vhd should not be broken
await compressedVhd.readHeaderAndFooter()
await compressedVhd.readBlockAllocationTable()
// check that footer and header are not modified
expect(compressedVhd.footer).toEqual(vhd.footer)
expect(compressedVhd.header).toEqual(vhd.header)
// their block content should not have changed
let counter = 0
for await (const block of compressedVhd.blocks()) {
const source = await vhd.readBlock(block.id)
expect(source.data.equals(block.data)).toEqual(true)
counter++
}
// neither the number of blocks
expect(counter).toEqual(2)
})
})

View File

@@ -0,0 +1,238 @@
'use strict'
const { beforeEach, afterEach, describe, it } = require('test')
const { strict: assert } = require('assert')
const execa = require('execa')
const fs = require('fs-extra')
const getStream = require('get-stream')
const { rimraf } = require('rimraf')
const tmp = require('tmp')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { Disposable, pFromCallback } = require('promise-toolbox')
const { randomBytes } = require('crypto')
const { VhdFile } = require('./VhdFile')
const { openVhd } = require('../openVhd')
const { SECTOR_SIZE } = require('../_constants')
const {
checkFile,
createRandomFile,
convertFromRawToVhd,
convertToVhdDirectory,
recoverRawContent,
} = require('../tests/utils')
let tempDir = null
let handler
let disposeHandler
describe('VhdFile', async () => {
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 rimraf(tempDir)
disposeHandler()
})
it('respects the checkSecondFooter flag', async () => {
const initalSize = 0
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
const size = await handler.getSize('randomfile.vhd')
const fd = await handler.openFile('randomfile.vhd', 'r+')
const buffer = Buffer.alloc(512, 0)
// add a fake footer at the end
handler.write(fd, buffer, size)
await handler.closeFile(fd)
// not using openVhd to be able to call readHeaderAndFooter separatly
const vhd = new VhdFile(handler, 'randomfile.vhd')
await assert.rejects(async () => await vhd.readHeaderAndFooter())
await assert.rejects(async () => await vhd.readHeaderAndFooter(true))
assert.equal(await vhd.readHeaderAndFooter(false), undefined)
})
it('blocks can be moved', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
const originalSize = await handler.getSize('randomfile')
const newVhd = new VhdFile(handler, 'randomfile.vhd')
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
await newVhd._freeFirstBlockSpace(8000000)
const recoveredFileName = `${tempDir}/recovered`
await recoverRawContent(vhdFileName, recoveredFileName, originalSize)
assert.equal((await fs.readFile(recoveredFileName)).equals(await fs.readFile(rawFileName)), true)
})
it('does not use the BAT MSB 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 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
const hugeWritePositionSectors = Math.pow(2, 31) + 200
await vhd.writeData(hugeWritePositionSectors, randomBuffer)
await checkFile(emptyFileName)
// here we are moving the first sector very far in the VHD to prove the BAT doesn't use signed int32
const hugePositionBytes = hugeWritePositionSectors * SECTOR_SIZE
await vhd._freeFirstBlockSpace(hugePositionBytes)
await vhd.writeFooter()
// we recover the data manually for speed reasons.
// fs.write() with offset is way faster than qemu-img when there is a 1.5To
// hole before the block of data
const recoveredFileName = `${tempDir}/recovered`
const recoveredFile = await fs.open(recoveredFileName, 'w')
try {
const vhd2 = new VhdFile(handler, 'empty.vhd')
await vhd2.readHeaderAndFooter()
await vhd2.readBlockAllocationTable()
for (let i = 0; i < vhd.header.maxTableEntries; i++) {
if (vhd.containsBlock(i)) {
const block = (await vhd2.readBlock(i)).data
await fs.write(recoveredFile, block, 0, block.length, vhd2.header.blockSize * i)
}
}
} finally {
fs.close(recoveredFile)
}
const recovered = await getStream.buffer(
await fs.createReadStream(recoveredFileName, {
start: hugePositionBytes,
end: hugePositionBytes + randomBuffer.length - 1,
})
)
assert.equal(recovered.equals(randomBuffer), true)
})
it('writes Data on empty file', async () => {
const mbOfRandom = 3
const rawFileName = `${tempDir}/randomfile`
const emptyFileName = `${tempDir}/empty.vhd`
await createRandomFile(rawFileName, mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M'])
const randomData = await fs.readFile(rawFileName)
const originalSize = await handler.getSize('randomfile')
const newVhd = new VhdFile(handler, 'empty.vhd')
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
await newVhd.writeData(0, randomData)
const recoveredFileName = `${tempDir}/recovered`
await recoverRawContent(emptyFileName, recoveredFileName, originalSize)
assert.equal((await fs.readFile(recoveredFileName)).equals(randomData), true)
})
it('writes Data in 2 non-overlaping operations', async () => {
const mbOfRandom = 3
const rawFileName = `${tempDir}/randomfile`
const emptyFileName = `${tempDir}/empty.vhd`
const recoveredFileName = `${tempDir}/recovered`
await createRandomFile(rawFileName, mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M'])
const randomData = await fs.readFile(rawFileName)
const originalSize = await handler.getSize('randomfile')
const newVhd = new VhdFile(handler, 'empty.vhd')
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
const splitPointSectors = 2
await newVhd.writeData(0, randomData.slice(0, splitPointSectors * 512))
await newVhd.writeData(splitPointSectors, randomData.slice(splitPointSectors * 512))
await recoverRawContent(emptyFileName, recoveredFileName, originalSize)
assert.equal((await fs.readFile(recoveredFileName)).equals(randomData), true)
})
it('writes Data in 2 overlaping operations', async () => {
const mbOfRandom = 3
const rawFileName = `${tempDir}/randomfile`
const emptyFileName = `${tempDir}/empty.vhd`
const recoveredFileName = `${tempDir}/recovered`
await createRandomFile(rawFileName, mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M'])
const randomData = await fs.readFile(rawFileName)
const originalSize = await handler.getSize('randomfile')
const newVhd = new VhdFile(handler, 'empty.vhd')
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
const endFirstWrite = 3
const startSecondWrite = 2
await newVhd.writeData(0, randomData.slice(0, endFirstWrite * 512))
await newVhd.writeData(startSecondWrite, randomData.slice(startSecondWrite * 512))
await recoverRawContent(emptyFileName, recoveredFileName, originalSize)
assert.equal((await fs.readFile(recoveredFileName)).equals(randomData), true)
})
it('an extend BAT and move blocks', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile`
const recoveredFileName = `${tempDir}/recovered`
const vhdFileName = `${tempDir}/randomfile.vhd`
await createRandomFile(rawFileName, initalSize)
await convertFromRawToVhd(rawFileName, vhdFileName)
const originalSize = await handler.getSize('randomfile')
const newVhd = new VhdFile(handler, 'randomfile.vhd')
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
await newVhd.ensureBatSize(2000)
await newVhd.writeBlockAllocationTable()
await recoverRawContent(vhdFileName, recoveredFileName, originalSize)
assert.equal((await fs.readFile(recoveredFileName)).equals(await fs.readFile(rawFileName)), true)
})
it('Can coalesce block', async () => {
const initalSize = 4
const parentrawFileName = `${tempDir}/randomfile`
const parentFileName = `${tempDir}/parent.vhd`
await createRandomFile(parentrawFileName, initalSize)
await convertFromRawToVhd(parentrawFileName, parentFileName)
const childrawFileName = `${tempDir}/randomfile`
const childFileName = `${tempDir}/childFile.vhd`
await createRandomFile(childrawFileName, initalSize)
await convertFromRawToVhd(childrawFileName, childFileName)
const childRawDirectoryName = `${tempDir}/randomFile2.vhd`
const childDirectoryFileName = `${tempDir}/childDirFile.vhd`
const childDirectoryName = `${tempDir}/childDir.vhd`
await createRandomFile(childRawDirectoryName, initalSize)
await convertFromRawToVhd(childRawDirectoryName, childDirectoryFileName)
await convertToVhdDirectory(childRawDirectoryName, childDirectoryFileName, childDirectoryName)
await Disposable.use(async function* () {
const parentVhd = yield openVhd(handler, 'parent.vhd', { flags: 'r+' })
await parentVhd.readBlockAllocationTable()
const childFileVhd = yield openVhd(handler, 'childFile.vhd')
await childFileVhd.readBlockAllocationTable()
const childDirectoryVhd = yield openVhd(handler, 'childDir.vhd')
await childDirectoryVhd.readBlockAllocationTable()
await parentVhd.mergeBlock(childFileVhd, 0)
await parentVhd.writeFooter()
await parentVhd.writeBlockAllocationTable()
let parentBlockData = (await parentVhd.readBlock(0)).data
let childBlockData = (await childFileVhd.readBlock(0)).data
assert.equal(parentBlockData.equals(childBlockData), true)
await parentVhd.mergeBlock(childDirectoryVhd, 0)
await parentVhd.writeFooter()
await parentVhd.writeBlockAllocationTable()
parentBlockData = (await parentVhd.readBlock(0)).data
childBlockData = (await childDirectoryVhd.readBlock(0)).data
assert.equal(parentBlockData.equals(childBlockData), true)
})
})
})

View File

@@ -1,237 +0,0 @@
'use strict'
/* eslint-env jest */
const execa = require('execa')
const fs = require('fs-extra')
const getStream = require('get-stream')
const tmp = require('tmp')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { Disposable, pFromCallback } = require('promise-toolbox')
const { randomBytes } = require('crypto')
const { rimraf } = require('rimraf')
const { VhdFile } = require('./VhdFile')
const { openVhd } = require('../openVhd')
const { SECTOR_SIZE } = require('../_constants')
const {
checkFile,
createRandomFile,
convertFromRawToVhd,
convertToVhdDirectory,
recoverRawContent,
} = 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 rimraf(tempDir)
disposeHandler()
})
test('respect the checkSecondFooter flag', async () => {
const initalSize = 0
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
const size = await handler.getSize('randomfile.vhd')
const fd = await handler.openFile('randomfile.vhd', 'r+')
const buffer = Buffer.alloc(512, 0)
// add a fake footer at the end
handler.write(fd, buffer, size)
await handler.closeFile(fd)
// not using openVhd to be able to call readHeaderAndFooter separatly
const vhd = new VhdFile(handler, 'randomfile.vhd')
await expect(async () => await vhd.readHeaderAndFooter()).rejects.toThrow()
await expect(async () => await vhd.readHeaderAndFooter(true)).rejects.toThrow()
await expect(await vhd.readHeaderAndFooter(false)).toEqual(undefined)
})
test('blocks can be moved', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
const originalSize = await handler.getSize('randomfile')
const newVhd = new VhdFile(handler, 'randomfile.vhd')
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
await newVhd._freeFirstBlockSpace(8000000)
const recoveredFileName = `${tempDir}/recovered`
await recoverRawContent(vhdFileName, recoveredFileName, originalSize)
expect((await fs.readFile(recoveredFileName)).equals(await fs.readFile(rawFileName))).toEqual(true)
})
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 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
const hugeWritePositionSectors = Math.pow(2, 31) + 200
await vhd.writeData(hugeWritePositionSectors, randomBuffer)
await checkFile(emptyFileName)
// here we are moving the first sector very far in the VHD to prove the BAT doesn't use signed int32
const hugePositionBytes = hugeWritePositionSectors * SECTOR_SIZE
await vhd._freeFirstBlockSpace(hugePositionBytes)
await vhd.writeFooter()
// we recover the data manually for speed reasons.
// fs.write() with offset is way faster than qemu-img when there is a 1.5To
// hole before the block of data
const recoveredFileName = `${tempDir}/recovered`
const recoveredFile = await fs.open(recoveredFileName, 'w')
try {
const vhd2 = new VhdFile(handler, 'empty.vhd')
await vhd2.readHeaderAndFooter()
await vhd2.readBlockAllocationTable()
for (let i = 0; i < vhd.header.maxTableEntries; i++) {
if (vhd.containsBlock(i)) {
const block = (await vhd2.readBlock(i)).data
await fs.write(recoveredFile, block, 0, block.length, vhd2.header.blockSize * i)
}
}
} finally {
fs.close(recoveredFile)
}
const recovered = await getStream.buffer(
await fs.createReadStream(recoveredFileName, {
start: hugePositionBytes,
end: hugePositionBytes + randomBuffer.length - 1,
})
)
expect(recovered.equals(randomBuffer)).toEqual(true)
})
test('writeData on empty file', async () => {
const mbOfRandom = 3
const rawFileName = `${tempDir}/randomfile`
const emptyFileName = `${tempDir}/empty.vhd`
await createRandomFile(rawFileName, mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M'])
const randomData = await fs.readFile(rawFileName)
const originalSize = await handler.getSize('randomfile')
const newVhd = new VhdFile(handler, 'empty.vhd')
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
await newVhd.writeData(0, randomData)
const recoveredFileName = `${tempDir}/recovered`
await recoverRawContent(emptyFileName, recoveredFileName, originalSize)
expect((await fs.readFile(recoveredFileName)).equals(randomData)).toEqual(true)
})
test('writeData in 2 non-overlaping operations', async () => {
const mbOfRandom = 3
const rawFileName = `${tempDir}/randomfile`
const emptyFileName = `${tempDir}/empty.vhd`
const recoveredFileName = `${tempDir}/recovered`
await createRandomFile(rawFileName, mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M'])
const randomData = await fs.readFile(rawFileName)
const originalSize = await handler.getSize('randomfile')
const newVhd = new VhdFile(handler, 'empty.vhd')
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
const splitPointSectors = 2
await newVhd.writeData(0, randomData.slice(0, splitPointSectors * 512))
await newVhd.writeData(splitPointSectors, randomData.slice(splitPointSectors * 512))
await recoverRawContent(emptyFileName, recoveredFileName, originalSize)
expect((await fs.readFile(recoveredFileName)).equals(randomData)).toEqual(true)
})
test('writeData in 2 overlaping operations', async () => {
const mbOfRandom = 3
const rawFileName = `${tempDir}/randomfile`
const emptyFileName = `${tempDir}/empty.vhd`
const recoveredFileName = `${tempDir}/recovered`
await createRandomFile(rawFileName, mbOfRandom)
await execa('qemu-img', ['create', '-fvpc', emptyFileName, mbOfRandom + 'M'])
const randomData = await fs.readFile(rawFileName)
const originalSize = await handler.getSize('randomfile')
const newVhd = new VhdFile(handler, 'empty.vhd')
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
const endFirstWrite = 3
const startSecondWrite = 2
await newVhd.writeData(0, randomData.slice(0, endFirstWrite * 512))
await newVhd.writeData(startSecondWrite, randomData.slice(startSecondWrite * 512))
await recoverRawContent(emptyFileName, recoveredFileName, originalSize)
expect((await fs.readFile(recoveredFileName)).equals(randomData)).toEqual(true)
})
test('BAT can be extended and blocks moved', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile`
const recoveredFileName = `${tempDir}/recovered`
const vhdFileName = `${tempDir}/randomfile.vhd`
await createRandomFile(rawFileName, initalSize)
await convertFromRawToVhd(rawFileName, vhdFileName)
const originalSize = await handler.getSize('randomfile')
const newVhd = new VhdFile(handler, 'randomfile.vhd')
await newVhd.readHeaderAndFooter()
await newVhd.readBlockAllocationTable()
await newVhd.ensureBatSize(2000)
await newVhd.writeBlockAllocationTable()
await recoverRawContent(vhdFileName, recoveredFileName, originalSize)
expect((await fs.readFile(recoveredFileName)).equals(await fs.readFile(rawFileName))).toEqual(true)
})
test('Can coalesce block', async () => {
const initalSize = 4
const parentrawFileName = `${tempDir}/randomfile`
const parentFileName = `${tempDir}/parent.vhd`
await createRandomFile(parentrawFileName, initalSize)
await convertFromRawToVhd(parentrawFileName, parentFileName)
const childrawFileName = `${tempDir}/randomfile`
const childFileName = `${tempDir}/childFile.vhd`
await createRandomFile(childrawFileName, initalSize)
await convertFromRawToVhd(childrawFileName, childFileName)
const childRawDirectoryName = `${tempDir}/randomFile2.vhd`
const childDirectoryFileName = `${tempDir}/childDirFile.vhd`
const childDirectoryName = `${tempDir}/childDir.vhd`
await createRandomFile(childRawDirectoryName, initalSize)
await convertFromRawToVhd(childRawDirectoryName, childDirectoryFileName)
await convertToVhdDirectory(childRawDirectoryName, childDirectoryFileName, childDirectoryName)
await Disposable.use(async function* () {
const parentVhd = yield openVhd(handler, 'parent.vhd', { flags: 'r+' })
await parentVhd.readBlockAllocationTable()
const childFileVhd = yield openVhd(handler, 'childFile.vhd')
await childFileVhd.readBlockAllocationTable()
const childDirectoryVhd = yield openVhd(handler, 'childDir.vhd')
await childDirectoryVhd.readBlockAllocationTable()
await parentVhd.mergeBlock(childFileVhd, 0)
await parentVhd.writeFooter()
await parentVhd.writeBlockAllocationTable()
let parentBlockData = (await parentVhd.readBlock(0)).data
let childBlockData = (await childFileVhd.readBlock(0)).data
expect(parentBlockData.equals(childBlockData)).toEqual(true)
await parentVhd.mergeBlock(childDirectoryVhd, 0)
await parentVhd.writeFooter()
await parentVhd.writeBlockAllocationTable()
parentBlockData = (await parentVhd.readBlock(0)).data
childBlockData = (await childDirectoryVhd.readBlock(0)).data
expect(parentBlockData.equals(childBlockData)).toEqual(true)
})
})

View File

@@ -0,0 +1,89 @@
'use strict'
const { beforeEach, afterEach, describe, it } = require('test')
const { strict: assert } = require('assert')
const { rimraf } = require('rimraf')
const tmp = require('tmp')
const { Disposable, pFromCallback } = require('promise-toolbox')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { SECTOR_SIZE, PLATFORMS } = require('../_constants')
const { createRandomFile, convertFromRawToVhd } = require('../tests/utils')
const { openVhd, chainVhd, VhdSynthetic } = require('..')
let tempDir = null
describe('VhdSynthetic', async () => {
beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb))
})
afterEach(async () => {
await rimraf(tempDir)
})
it('can read block and parent locator from a synthetic vhd', async () => {
const bigRawFileName = `/bigrandomfile`
await createRandomFile(`${tempDir}/${bigRawFileName}`, 8)
const bigVhdFileName = `/bigrandomfile.vhd`
await convertFromRawToVhd(`${tempDir}/${bigRawFileName}`, `${tempDir}/${bigVhdFileName}`)
const smallRawFileName = `/smallrandomfile`
await createRandomFile(`${tempDir}/${smallRawFileName}`, 4)
const smallVhdFileName = `/smallrandomfile.vhd`
await convertFromRawToVhd(`${tempDir}/${smallRawFileName}`, `${tempDir}/${smallVhdFileName}`)
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
// ensure the two VHD are linked, with the child of type DISK_TYPES.DIFFERENCING
await chainVhd(handler, bigVhdFileName, handler, smallVhdFileName, true)
const bigVhd = yield openVhd(handler, bigVhdFileName)
await bigVhd.readBlockAllocationTable()
// add parent locato
// this will also scramble the block inside the vhd files
await bigVhd.writeParentLocator({
id: 0,
platformCode: PLATFORMS.W2KU,
data: Buffer.from('I am in the big one'),
})
// header changed since thre is a new parent locator
await bigVhd.writeHeader()
// the footer at the end changed since the block have been moved
await bigVhd.writeFooter()
await bigVhd.readHeaderAndFooter()
const syntheticVhd = yield VhdSynthetic.open(handler, [bigVhdFileName, smallVhdFileName])
await syntheticVhd.readBlockAllocationTable()
assert.deepEqual(syntheticVhd.header.diskType, bigVhd.header.diskType)
assert.deepEqual(syntheticVhd.header.parentTimestamp, bigVhd.header.parentTimestamp)
// first two block should be from small
const buf = Buffer.alloc(syntheticVhd.sectorsPerBlock * SECTOR_SIZE, 0)
let content = (await syntheticVhd.readBlock(0)).data
await handler.read(smallRawFileName, buf, 0)
assert.equal(content.equals(buf), true)
content = (await syntheticVhd.readBlock(1)).data
await handler.read(smallRawFileName, buf, buf.length)
assert.equal(content.equals(buf), true)
// the next one from big
content = (await syntheticVhd.readBlock(2)).data
await handler.read(bigRawFileName, buf, buf.length * 2)
assert.equal(content.equals(buf), true)
content = (await syntheticVhd.readBlock(3)).data
await handler.read(bigRawFileName, buf, buf.length * 3)
assert.equal(content.equals(buf), true)
// the parent locator should the one of the root vhd
const parentLocator = await syntheticVhd.readParentLocator(0)
assert.equal(parentLocator.platformCode, PLATFORMS.W2KU)
})
})
})

View File

@@ -1,88 +0,0 @@
'use strict'
/* eslint-env jest */
const tmp = require('tmp')
const { Disposable, pFromCallback } = require('promise-toolbox')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { rimraf } = require('rimraf')
const { SECTOR_SIZE, PLATFORMS } = require('../_constants')
const { createRandomFile, convertFromRawToVhd } = require('../tests/utils')
const { openVhd, chainVhd, VhdSynthetic } = require('..')
let tempDir = null
jest.setTimeout(60000)
beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb))
})
afterEach(async () => {
await rimraf(tempDir)
})
test('It can read block and parent locator from a synthetic vhd', async () => {
const bigRawFileName = `/bigrandomfile`
await createRandomFile(`${tempDir}/${bigRawFileName}`, 8)
const bigVhdFileName = `/bigrandomfile.vhd`
await convertFromRawToVhd(`${tempDir}/${bigRawFileName}`, `${tempDir}/${bigVhdFileName}`)
const smallRawFileName = `/smallrandomfile`
await createRandomFile(`${tempDir}/${smallRawFileName}`, 4)
const smallVhdFileName = `/smallrandomfile.vhd`
await convertFromRawToVhd(`${tempDir}/${smallRawFileName}`, `${tempDir}/${smallVhdFileName}`)
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
// ensure the two VHD are linked, with the child of type DISK_TYPES.DIFFERENCING
await chainVhd(handler, bigVhdFileName, handler, smallVhdFileName, true)
const bigVhd = yield openVhd(handler, bigVhdFileName)
await bigVhd.readBlockAllocationTable()
// add parent locato
// this will also scramble the block inside the vhd files
await bigVhd.writeParentLocator({
id: 0,
platformCode: PLATFORMS.W2KU,
data: Buffer.from('I am in the big one'),
})
// header changed since thre is a new parent locator
await bigVhd.writeHeader()
// the footer at the end changed since the block have been moved
await bigVhd.writeFooter()
await bigVhd.readHeaderAndFooter()
const syntheticVhd = yield VhdSynthetic.open(handler, [bigVhdFileName, smallVhdFileName])
await syntheticVhd.readBlockAllocationTable()
expect(syntheticVhd.header.diskType).toEqual(bigVhd.header.diskType)
expect(syntheticVhd.header.parentTimestamp).toEqual(bigVhd.header.parentTimestamp)
// first two block should be from small
const buf = Buffer.alloc(syntheticVhd.sectorsPerBlock * SECTOR_SIZE, 0)
let content = (await syntheticVhd.readBlock(0)).data
await handler.read(smallRawFileName, buf, 0)
expect(content.equals(buf)).toEqual(true)
content = (await syntheticVhd.readBlock(1)).data
await handler.read(smallRawFileName, buf, buf.length)
expect(content.equals(buf)).toEqual(true)
// the next one from big
content = (await syntheticVhd.readBlock(2)).data
await handler.read(bigRawFileName, buf, buf.length * 2)
expect(content.equals(buf)).toEqual(true)
content = (await syntheticVhd.readBlock(3)).data
await handler.read(bigRawFileName, buf, buf.length * 3)
expect(content.equals(buf)).toEqual(true)
// the parent locator should the one of the root vhd
const parentLocator = await syntheticVhd.readParentLocator(0)
expect(parentLocator.platformCode).toEqual(PLATFORMS.W2KU)
})
})

View File

@@ -1,6 +1,7 @@
'use strict'
/* eslint-env jest */
const { test } = require('test')
const { createFooter } = require('./_createFooterHeader')
test('createFooter() does not crash', () => {

View File

@@ -0,0 +1,77 @@
'use strict'
const { beforeEach, afterEach, describe, it } = require('test')
const { strict: assert } = require('assert')
const { rimraf } = require('rimraf')
const tmp = require('tmp')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { Disposable, pFromCallback } = require('promise-toolbox')
const { isVhdAlias, resolveVhdAlias } = require('./aliases')
const { ALIAS_MAX_PATH_LENGTH } = require('./_constants')
let tempDir
describe('isVhdAlias()', async () => {
beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb))
})
afterEach(async () => {
await rimraf(tempDir)
})
it('recognizes only *.alias.vhd files', () => {
assert.equal(isVhdAlias('filename.alias.vhd'), true)
assert.equal(isVhdAlias('alias.vhd'), false)
assert.equal(isVhdAlias('filename.vhd'), false)
assert.equal(isVhdAlias('filename.alias.vhd.other'), false)
})
})
describe('resolveVhdAlias()', async () => {
beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb))
})
afterEach(async () => {
await rimraf(tempDir)
})
it('returns the path in argument for a non alias file ', async () => {
assert.equal(await resolveVhdAlias(null, 'filename.vhd'), 'filename.vhd')
})
it('gets the path of the target file for an alias', async () => {
await Disposable.use(async function* () {
// same directory
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
const alias = `alias.alias.vhd`
await handler.writeFile(alias, 'target.vhd')
assert.equal(await resolveVhdAlias(handler, alias), `target.vhd`)
// different directory
await handler.mkdir(`sub`)
await handler.writeFile(alias, 'sub/target.vhd', { flags: 'w' })
assert.equal(await resolveVhdAlias(handler, alias), `sub/target.vhd`)
})
})
it('throws an error an alias to an alias', async () => {
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
const alias = `alias.alias.vhd`
const target = `target.alias.vhd`
await handler.writeFile(alias, target)
await assert.rejects(async () => await resolveVhdAlias(handler, alias), Error)
})
})
it('throws an error on a file too big ', async () => {
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
await handler.writeFile('toobig.alias.vhd', Buffer.alloc(ALIAS_MAX_PATH_LENGTH + 1, 0))
await assert.rejects(async () => await resolveVhdAlias(handler, 'toobig.alias.vhd'), Error)
})
})
})

View File

@@ -1,66 +0,0 @@
'use strict'
/* eslint-env jest */
const tmp = require('tmp')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { Disposable, pFromCallback } = require('promise-toolbox')
const { rimraf } = require('rimraf')
const { isVhdAlias, resolveVhdAlias } = require('./aliases')
const { ALIAS_MAX_PATH_LENGTH } = require('./_constants')
let tempDir
jest.setTimeout(60000)
beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb))
})
afterEach(async () => {
await rimraf(tempDir)
})
test('is vhd alias recognize only *.alias.vhd files', () => {
expect(isVhdAlias('filename.alias.vhd')).toEqual(true)
expect(isVhdAlias('alias.vhd')).toEqual(false)
expect(isVhdAlias('filename.vhd')).toEqual(false)
expect(isVhdAlias('filename.alias.vhd.other')).toEqual(false)
})
test('resolve return the path in argument for a non alias file ', async () => {
expect(await resolveVhdAlias(null, 'filename.vhd')).toEqual('filename.vhd')
})
test('resolve get the path of the target file for an alias', async () => {
await Disposable.use(async function* () {
// same directory
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
const alias = `alias.alias.vhd`
await handler.writeFile(alias, 'target.vhd')
await expect(await resolveVhdAlias(handler, alias)).toEqual(`target.vhd`)
// different directory
await handler.mkdir(`sub`)
await handler.writeFile(alias, 'sub/target.vhd', { flags: 'w' })
await expect(await resolveVhdAlias(handler, alias)).toEqual(`sub/target.vhd`)
})
})
test('resolve throws an error an alias to an alias', async () => {
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
const alias = `alias.alias.vhd`
const target = `target.alias.vhd`
await handler.writeFile(alias, target)
await expect(async () => await resolveVhdAlias(handler, alias)).rejects.toThrow(Error)
})
})
test('resolve throws an error on a file too big ', async () => {
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
await handler.writeFile('toobig.alias.vhd', Buffer.alloc(ALIAS_MAX_PATH_LENGTH + 1, 0))
await expect(async () => await resolveVhdAlias(handler, 'toobig.alias.vhd')).rejects.toThrow(Error)
})
})

View File

@@ -1,6 +1,7 @@
'use strict'
/* eslint-env jest */
const { beforeEach, afterEach, describe, it } = require('test')
const { strict: assert } = require('assert')
const execa = require('execa')
const fs = require('fs-extra')
@@ -12,22 +13,22 @@ const { pipeline } = require('readable-stream')
const { rimraf } = require('rimraf')
const createVhdStreamWithLength = require('./createVhdStreamWithLength.js')
const { FOOTER_SIZE } = require('./_constants')
const { createRandomFile, convertFromRawToVhd, convertFromVhdToRaw } = require('./tests/utils')
let tempDir = null
beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb))
})
afterEach(async () => {
await rimraf(tempDir)
})
const { FOOTER_SIZE } = require('./_constants.js')
const { createRandomFile, convertFromRawToVhd, convertFromVhdToRaw } = require('./tests/utils.js')
const forOwn = (object, cb) => Object.keys(object).forEach(key => cb(object[key], key, object))
describe('createVhdStreamWithLength', () => {
let tempDir = null
beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb))
})
afterEach(async () => {
await rimraf(tempDir)
})
forOwn(
{
// qemu-img requires this length or it fill with null bytes which breaks
@@ -52,7 +53,7 @@ describe('createVhdStreamWithLength', () => {
// ensure the guessed length correspond to the stream length
const { size: outputSize } = await fs.stat(outputVhd)
expect(length).toEqual(outputSize)
assert.equal(length, outputSize)
// ensure the generated VHD is correct and contains the same data
const outputRaw = `${tempDir}/output.raw`
@@ -83,14 +84,14 @@ describe('createVhdStreamWithLength', () => {
await pFromCallback(cb => endOfFile.end(footer, cb))
const { size: longerSize } = await fs.stat(vhdName)
// check input file has been lengthened
expect(longerSize).toEqual(vhdSize + FOOTER_SIZE)
assert.equal(longerSize, vhdSize + FOOTER_SIZE)
const result = await createVhdStreamWithLength(await createReadStream(vhdName))
expect(result.length).toEqual(vhdSize)
assert.equal(result.length, vhdSize)
const outputFileStream = await createWriteStream(outputVhdName)
await pFromCallback(cb => pipeline(result, outputFileStream, cb))
const { size: outputSize } = await fs.stat(outputVhdName)
// check out file has been shortened again
expect(outputSize).toEqual(vhdSize)
assert.equal(outputSize, vhdSize)
await execa('qemu-img', ['compare', outputVhdName, vhdName])
})
})

View File

@@ -0,0 +1,365 @@
'use strict'
const { beforeEach, afterEach, describe, it } = require('test')
const { strict: assert } = require('assert')
const fs = require('fs-extra')
const { rimraf } = require('rimraf')
const tmp = require('tmp')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { pFromCallback, Disposable } = require('promise-toolbox')
const { VhdFile, chainVhd, openVhd, VhdAbstract } = require('./index')
const { mergeVhdChain } = require('./merge')
const { checkFile, createRandomFile, convertFromRawToVhd } = require('./tests/utils')
let tempDir = null
let handler
let disposeHandler
describe('mergeVhdChain()', async () => {
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 rimraf(tempDir)
disposeHandler()
})
it('works in normal cases', async () => {
const mbOfFather = 8
const mbOfChildren = 4
const parentRandomFileName = `randomfile`
const childRandomFileName = `small_randomfile`
const parentFileName = `parent.vhd`
const child1FileName = `child1.vhd`
await createRandomFile(`${tempDir}/${parentRandomFileName}`, mbOfFather)
await convertFromRawToVhd(`${tempDir}/${parentRandomFileName}`, `${tempDir}/${parentFileName}`)
await createRandomFile(`${tempDir}/${childRandomFileName}`, mbOfChildren)
await convertFromRawToVhd(`${tempDir}/${childRandomFileName}`, `${tempDir}/${child1FileName}`)
await chainVhd(handler, parentFileName, handler, child1FileName, true)
await checkFile(`${tempDir}/${parentFileName}`)
// merge
await mergeVhdChain(handler, [parentFileName, child1FileName])
// check that the merged vhd is still valid
await checkFile(`${tempDir}/${child1FileName}`)
const parentVhd = new VhdFile(handler, child1FileName)
await parentVhd.readHeaderAndFooter()
await parentVhd.readBlockAllocationTable()
let offset = 0
// check that the data are the same as source
for await (const block of parentVhd.blocks()) {
const blockContent = block.data
const file = offset < mbOfChildren * 1024 * 1024 ? childRandomFileName : parentRandomFileName
const buffer = Buffer.alloc(blockContent.length)
const fd = await fs.open(`${tempDir}/${file}`, 'r')
await fs.read(fd, buffer, 0, buffer.length, offset)
assert.equal(buffer.equals(blockContent), true)
offset += parentVhd.header.blockSize
}
})
it('can resume a simple merge', async () => {
const mbOfFather = 8
const mbOfChildren = 4
const parentRandomFileName = `${tempDir}/randomfile`
const childRandomFileName = `${tempDir}/small_randomfile`
await createRandomFile(`${tempDir}/randomfile`, mbOfFather)
await convertFromRawToVhd(`${tempDir}/randomfile`, `${tempDir}/parent.vhd`)
const parentVhd = new VhdFile(handler, 'parent.vhd')
await parentVhd.readHeaderAndFooter()
await createRandomFile(`${tempDir}/small_randomfile`, mbOfChildren)
await convertFromRawToVhd(`${tempDir}/small_randomfile`, `${tempDir}/child1.vhd`)
await chainVhd(handler, 'parent.vhd', handler, 'child1.vhd', true)
const childVhd = new VhdFile(handler, 'child1.vhd')
await childVhd.readHeaderAndFooter()
await handler.writeFile(
'.parent.vhd.merge.json',
JSON.stringify({
parent: {
header: parentVhd.header.checksum,
},
child: {
header: 'NOT CHILD HEADER ',
},
})
)
// expect merge to fail since child header is not ok
await assert.rejects(async () => await mergeVhdChain(handler, ['parent.vhd', 'child1.vhd']))
await handler.unlink('.parent.vhd.merge.json')
await handler.writeFile(
'.parent.vhd.merge.json',
JSON.stringify({
parent: {
header: 'NOT PARENT HEADER',
},
child: {
header: childVhd.header.checksum,
},
})
)
// expect merge to fail since parent header is not ok
await assert.rejects(async () => await mergeVhdChain(handler, ['parent.vhd', 'child1.vhd']))
// break the end footer of parent
const size = await handler.getSize('parent.vhd')
const fd = await handler.openFile('parent.vhd', 'r+')
const buffer = Buffer.alloc(512, 0)
// add a fake footer at the end
handler.write(fd, buffer, size)
await handler.closeFile(fd)
// check vhd should fail
await assert.rejects(async () => await parentVhd.readHeaderAndFooter())
await handler.unlink('.parent.vhd.merge.json')
await handler.writeFile(
'.parent.vhd.merge.json',
JSON.stringify({
parent: {
header: parentVhd.header.checksum,
},
child: {
header: childVhd.header.checksum,
},
currentBlock: 1,
})
)
// really merge
await mergeVhdChain(handler, ['parent.vhd', 'child1.vhd'])
// reload header footer and block allocation table , they should succed
await childVhd.readHeaderAndFooter()
await childVhd.readBlockAllocationTable()
let offset = 0
// check that the data are the same as source
for await (const block of childVhd.blocks()) {
const blockContent = block.data
// first block is marked as already merged, should not be modified
// second block should come from children
// then two block only in parent
const file = block.id === 1 ? childRandomFileName : parentRandomFileName
const buffer = Buffer.alloc(blockContent.length)
const fd = await fs.open(file, 'r')
await fs.read(fd, buffer, 0, buffer.length, offset)
assert.equal(buffer.equals(blockContent), true)
offset += childVhd.header.blockSize
}
})
it('can resume a failed renaming', async () => {
const mbOfFather = 8
const mbOfChildren = 4
const parentRandomFileName = `${tempDir}/randomfile`
const parentName = 'parentvhd.alias.vhd'
const childName = 'childvhd.alias.vhd'
await createRandomFile(`${tempDir}/randomfile`, mbOfFather)
await convertFromRawToVhd(`${tempDir}/randomfile`, `${tempDir}/parentdata.vhd`)
VhdAbstract.createAlias(handler, parentName, 'parentdata.vhd')
const parentVhd = new VhdFile(handler, 'parentdata.vhd')
await parentVhd.readHeaderAndFooter()
await createRandomFile(`${tempDir}/small_randomfile`, mbOfChildren)
await convertFromRawToVhd(`${tempDir}/small_randomfile`, `${tempDir}/childdata.vhd`)
await chainVhd(handler, 'parentdata.vhd', handler, 'childdata.vhd', true)
VhdAbstract.createAlias(handler, childName, 'childdata.vhd')
const childVhd = new VhdFile(handler, 'childdata.vhd')
await childVhd.readHeaderAndFooter()
await handler.writeFile(
`.${parentName}.merge.json`,
JSON.stringify({
parent: {
header: parentVhd.header.checksum,
},
child: {
header: childVhd.header.checksum,
},
step: 'cleanupVhds',
})
)
// expect merge to succeed
await mergeVhdChain(handler, [parentName, childName])
// parent have been renamed
assert.equal(await fs.exists(`${tempDir}/${parentName}`), false)
assert.equal(await fs.exists(`${tempDir}/${childName}`), true)
assert.equal(await fs.exists(`${tempDir}/.${parentName}.merge.json`), false)
// we shouldn't have moved the data, but the child data should have been merged into parent
assert.equal(await fs.exists(`${tempDir}/parentdata.vhd`), true)
assert.equal(await fs.exists(`${tempDir}/childdata.vhd`), false)
Disposable.use(openVhd(handler, childName), async mergedVhd => {
await mergedVhd.readBlockAllocationTable()
// the resume is at the step 'cleanupVhds' it should not have merged blocks and should still contains parent data
let offset = 0
const fd = await fs.open(parentRandomFileName, 'r')
for await (const block of mergedVhd.blocks()) {
const blockContent = block.data
const buffer = Buffer.alloc(blockContent.length)
await fs.read(fd, buffer, 0, buffer.length, offset)
assert.equal(buffer.equals(blockContent), true)
offset += childVhd.header.blockSize
}
})
// merge succeed if renaming was already done
await handler.writeFile(
`.${parentName}.merge.json`,
JSON.stringify({
parent: {
header: parentVhd.header.checksum,
},
child: {
header: childVhd.header.checksum,
},
step: 'cleanupVhds',
})
)
await mergeVhdChain(handler, [parentName, childName])
assert.equal(await fs.exists(`${tempDir}/${parentName}`), false)
assert.equal(await fs.exists(`${tempDir}/${childName}`), true)
// we shouldn't have moved the data, but the child data should have been merged into parent
assert.equal(await fs.exists(`${tempDir}/parentdata.vhd`), true)
assert.equal(await fs.exists(`${tempDir}/childdata.vhd`), false)
assert.equal(await fs.exists(`${tempDir}/.${parentName}.merge.json`), false)
})
it('can resume a multiple merge', async () => {
const mbOfFather = 8
const mbOfChildren = 6
const mbOfGrandChildren = 4
const parentRandomFileName = `${tempDir}/randomfile`
const childRandomFileName = `${tempDir}/small_randomfile`
const grandChildRandomFileName = `${tempDir}/another_small_randomfile`
const parentFileName = `${tempDir}/parent.vhd`
const childFileName = `${tempDir}/child.vhd`
const grandChildFileName = `${tempDir}/grandchild.vhd`
await createRandomFile(parentRandomFileName, mbOfFather)
await convertFromRawToVhd(parentRandomFileName, parentFileName)
await createRandomFile(childRandomFileName, mbOfChildren)
await convertFromRawToVhd(childRandomFileName, childFileName)
await chainVhd(handler, 'parent.vhd', handler, 'child.vhd', true)
await createRandomFile(grandChildRandomFileName, mbOfGrandChildren)
await convertFromRawToVhd(grandChildRandomFileName, grandChildFileName)
await chainVhd(handler, 'child.vhd', handler, 'grandchild.vhd', true)
const parentVhd = new VhdFile(handler, 'parent.vhd')
await parentVhd.readHeaderAndFooter()
const childVhd = new VhdFile(handler, 'child.vhd')
await childVhd.readHeaderAndFooter()
const grandChildVhd = new VhdFile(handler, 'grandchild.vhd')
await grandChildVhd.readHeaderAndFooter()
await handler.writeFile(
`.parent.vhd.merge.json`,
JSON.stringify({
parent: {
header: parentVhd.header.checksum,
},
child: {
header: childVhd.header.checksum,
},
currentBlock: 1,
})
)
// should fail since the merge state file has only data of parent and child
await assert.rejects(async () => await mergeVhdChain(handler, ['parent.vhd', 'child.vhd', 'grandchild.vhd']))
// merge
await handler.unlink(`.parent.vhd.merge.json`)
await handler.writeFile(
`.parent.vhd.merge.json`,
JSON.stringify({
parent: {
header: parentVhd.header.checksum,
},
child: {
header: grandChildVhd.header.checksum,
},
currentBlock: 1,
childPath: ['child.vhd', 'grandchild.vhd'],
})
)
// it should succeed
await mergeVhdChain(handler, ['parent.vhd', 'child.vhd', 'grandchild.vhd'], { removeUnused: true })
assert.equal(await fs.exists(`${tempDir}/parent.vhd`), false)
assert.equal(await fs.exists(`${tempDir}/child.vhd`), false)
assert.equal(await fs.exists(`${tempDir}/grandchild.vhd`), true)
assert.equal(await fs.exists(`${tempDir}/.parent.vhd.merge.json`), false)
})
it('merges multiple children in one pass', async () => {
const mbOfFather = 8
const mbOfChildren = 6
const mbOfGrandChildren = 4
const parentRandomFileName = `${tempDir}/randomfile`
const childRandomFileName = `${tempDir}/small_randomfile`
const grandChildRandomFileName = `${tempDir}/another_small_randomfile`
const parentFileName = `${tempDir}/parent.vhd`
const childFileName = `${tempDir}/child.vhd`
const grandChildFileName = `${tempDir}/grandchild.vhd`
await createRandomFile(parentRandomFileName, mbOfFather)
await convertFromRawToVhd(parentRandomFileName, parentFileName)
await createRandomFile(childRandomFileName, mbOfChildren)
await convertFromRawToVhd(childRandomFileName, childFileName)
await chainVhd(handler, 'parent.vhd', handler, 'child.vhd', true)
await createRandomFile(grandChildRandomFileName, mbOfGrandChildren)
await convertFromRawToVhd(grandChildRandomFileName, grandChildFileName)
await chainVhd(handler, 'child.vhd', handler, 'grandchild.vhd', true)
// merge
await mergeVhdChain(handler, ['parent.vhd', 'child.vhd', 'grandchild.vhd'])
// check that vhd is still valid
await checkFile(grandChildFileName)
const parentVhd = new VhdFile(handler, 'grandchild.vhd')
await parentVhd.readHeaderAndFooter()
await parentVhd.readBlockAllocationTable()
let offset = 0
// check that the data are the same as source
for await (const block of parentVhd.blocks()) {
const blockContent = block.data
let file = parentRandomFileName
if (offset < mbOfGrandChildren * 1024 * 1024) {
file = grandChildRandomFileName
} else if (offset < mbOfChildren * 1024 * 1024) {
file = childRandomFileName
}
const buffer = Buffer.alloc(blockContent.length)
const fd = await fs.open(file, 'r')
await fs.read(fd, buffer, 0, buffer.length, offset)
assert.equal(buffer.equals(blockContent), true)
offset += parentVhd.header.blockSize
}
})
})

View File

@@ -1,365 +0,0 @@
'use strict'
/* eslint-env jest */
const fs = require('fs-extra')
const tmp = require('tmp')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { pFromCallback, Disposable } = require('promise-toolbox')
const { rimraf } = require('rimraf')
const { VhdFile, chainVhd, openVhd, VhdAbstract } = require('./index')
const { 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 rimraf(tempDir)
disposeHandler()
})
test('merge works in normal cases', async () => {
const mbOfFather = 8
const mbOfChildren = 4
const parentRandomFileName = `randomfile`
const childRandomFileName = `small_randomfile`
const parentFileName = `parent.vhd`
const child1FileName = `child1.vhd`
await createRandomFile(`${tempDir}/${parentRandomFileName}`, mbOfFather)
await convertFromRawToVhd(`${tempDir}/${parentRandomFileName}`, `${tempDir}/${parentFileName}`)
await createRandomFile(`${tempDir}/${childRandomFileName}`, mbOfChildren)
await convertFromRawToVhd(`${tempDir}/${childRandomFileName}`, `${tempDir}/${child1FileName}`)
await chainVhd(handler, parentFileName, handler, child1FileName, true)
await checkFile(`${tempDir}/${parentFileName}`)
// merge
await mergeVhdChain(handler, [parentFileName, child1FileName])
// check that the merged vhd is still valid
await checkFile(`${tempDir}/${child1FileName}`)
const parentVhd = new VhdFile(handler, child1FileName)
await parentVhd.readHeaderAndFooter()
await parentVhd.readBlockAllocationTable()
let offset = 0
// check that the data are the same as source
for await (const block of parentVhd.blocks()) {
const blockContent = block.data
const file = offset < mbOfChildren * 1024 * 1024 ? childRandomFileName : parentRandomFileName
const buffer = Buffer.alloc(blockContent.length)
const fd = await fs.open(`${tempDir}/${file}`, 'r')
await fs.read(fd, buffer, 0, buffer.length, offset)
expect(buffer.equals(blockContent)).toEqual(true)
offset += parentVhd.header.blockSize
}
})
test('it can resume a simple merge ', async () => {
const mbOfFather = 8
const mbOfChildren = 4
const parentRandomFileName = `${tempDir}/randomfile`
const childRandomFileName = `${tempDir}/small_randomfile`
await createRandomFile(`${tempDir}/randomfile`, mbOfFather)
await convertFromRawToVhd(`${tempDir}/randomfile`, `${tempDir}/parent.vhd`)
const parentVhd = new VhdFile(handler, 'parent.vhd')
await parentVhd.readHeaderAndFooter()
await createRandomFile(`${tempDir}/small_randomfile`, mbOfChildren)
await convertFromRawToVhd(`${tempDir}/small_randomfile`, `${tempDir}/child1.vhd`)
await chainVhd(handler, 'parent.vhd', handler, 'child1.vhd', true)
const childVhd = new VhdFile(handler, 'child1.vhd')
await childVhd.readHeaderAndFooter()
await handler.writeFile(
'.parent.vhd.merge.json',
JSON.stringify({
parent: {
header: parentVhd.header.checksum,
},
child: {
header: 'NOT CHILD HEADER ',
},
})
)
// expect merge to fail since child header is not ok
await expect(async () => await mergeVhdChain(handler, ['parent.vhd', 'child1.vhd'])).rejects.toThrow()
await handler.unlink('.parent.vhd.merge.json')
await handler.writeFile(
'.parent.vhd.merge.json',
JSON.stringify({
parent: {
header: 'NOT PARENT HEADER',
},
child: {
header: childVhd.header.checksum,
},
})
)
// expect merge to fail since parent header is not ok
await expect(async () => await mergeVhdChain(handler, ['parent.vhd', 'child1.vhd'])).rejects.toThrow()
// break the end footer of parent
const size = await handler.getSize('parent.vhd')
const fd = await handler.openFile('parent.vhd', 'r+')
const buffer = Buffer.alloc(512, 0)
// add a fake footer at the end
handler.write(fd, buffer, size)
await handler.closeFile(fd)
// check vhd should fail
await expect(async () => await parentVhd.readHeaderAndFooter()).rejects.toThrow()
await handler.unlink('.parent.vhd.merge.json')
await handler.writeFile(
'.parent.vhd.merge.json',
JSON.stringify({
parent: {
header: parentVhd.header.checksum,
},
child: {
header: childVhd.header.checksum,
},
currentBlock: 1,
})
)
// really merge
await mergeVhdChain(handler, ['parent.vhd', 'child1.vhd'])
// reload header footer and block allocation table , they should succed
await childVhd.readHeaderAndFooter()
await childVhd.readBlockAllocationTable()
let offset = 0
// check that the data are the same as source
for await (const block of childVhd.blocks()) {
const blockContent = block.data
// first block is marked as already merged, should not be modified
// second block should come from children
// then two block only in parent
const file = block.id === 1 ? childRandomFileName : parentRandomFileName
const buffer = Buffer.alloc(blockContent.length)
const fd = await fs.open(file, 'r')
await fs.read(fd, buffer, 0, buffer.length, offset)
expect(buffer.equals(blockContent)).toEqual(true)
offset += childVhd.header.blockSize
}
})
test('it can resume a failed renaming', async () => {
const mbOfFather = 8
const mbOfChildren = 4
const parentRandomFileName = `${tempDir}/randomfile`
const parentName = 'parentvhd.alias.vhd'
const childName = 'childvhd.alias.vhd'
await createRandomFile(`${tempDir}/randomfile`, mbOfFather)
await convertFromRawToVhd(`${tempDir}/randomfile`, `${tempDir}/parentdata.vhd`)
VhdAbstract.createAlias(handler, parentName, 'parentdata.vhd')
const parentVhd = new VhdFile(handler, 'parentdata.vhd')
await parentVhd.readHeaderAndFooter()
await createRandomFile(`${tempDir}/small_randomfile`, mbOfChildren)
await convertFromRawToVhd(`${tempDir}/small_randomfile`, `${tempDir}/childdata.vhd`)
await chainVhd(handler, 'parentdata.vhd', handler, 'childdata.vhd', true)
VhdAbstract.createAlias(handler, childName, 'childdata.vhd')
const childVhd = new VhdFile(handler, 'childdata.vhd')
await childVhd.readHeaderAndFooter()
await handler.writeFile(
`.${parentName}.merge.json`,
JSON.stringify({
parent: {
header: parentVhd.header.checksum,
},
child: {
header: childVhd.header.checksum,
},
step: 'cleanupVhds',
})
)
// expect merge to succeed
await mergeVhdChain(handler, [parentName, childName])
// parent have been renamed
expect(await fs.exists(`${tempDir}/${parentName}`)).toBeFalsy()
expect(await fs.exists(`${tempDir}/${childName}`)).toBeTruthy()
expect(await fs.exists(`${tempDir}/.${parentName}.merge.json`)).toBeFalsy()
// we shouldn't have moved the data, but the child data should have been merged into parent
expect(await fs.exists(`${tempDir}/parentdata.vhd`)).toBeTruthy()
expect(await fs.exists(`${tempDir}/childdata.vhd`)).toBeFalsy()
Disposable.use(openVhd(handler, childName), async mergedVhd => {
await mergedVhd.readBlockAllocationTable()
// the resume is at the step 'cleanupVhds' it should not have merged blocks and should still contains parent data
let offset = 0
const fd = await fs.open(parentRandomFileName, 'r')
for await (const block of mergedVhd.blocks()) {
const blockContent = block.data
const buffer = Buffer.alloc(blockContent.length)
await fs.read(fd, buffer, 0, buffer.length, offset)
expect(buffer.equals(blockContent)).toEqual(true)
offset += childVhd.header.blockSize
}
})
// merge succeed if renaming was already done
await handler.writeFile(
`.${parentName}.merge.json`,
JSON.stringify({
parent: {
header: parentVhd.header.checksum,
},
child: {
header: childVhd.header.checksum,
},
step: 'cleanupVhds',
})
)
await mergeVhdChain(handler, [parentName, childName])
expect(await fs.exists(`${tempDir}/${parentName}`)).toBeFalsy()
expect(await fs.exists(`${tempDir}/${childName}`)).toBeTruthy()
// we shouldn't have moved the data, but the child data should have been merged into parent
expect(await fs.exists(`${tempDir}/parentdata.vhd`)).toBeTruthy()
expect(await fs.exists(`${tempDir}/childdata.vhd`)).toBeFalsy()
expect(await fs.exists(`${tempDir}/.${parentName}.merge.json`)).toBeFalsy()
})
test('it can resume a multiple merge ', async () => {
const mbOfFather = 8
const mbOfChildren = 6
const mbOfGrandChildren = 4
const parentRandomFileName = `${tempDir}/randomfile`
const childRandomFileName = `${tempDir}/small_randomfile`
const grandChildRandomFileName = `${tempDir}/another_small_randomfile`
const parentFileName = `${tempDir}/parent.vhd`
const childFileName = `${tempDir}/child.vhd`
const grandChildFileName = `${tempDir}/grandchild.vhd`
await createRandomFile(parentRandomFileName, mbOfFather)
await convertFromRawToVhd(parentRandomFileName, parentFileName)
await createRandomFile(childRandomFileName, mbOfChildren)
await convertFromRawToVhd(childRandomFileName, childFileName)
await chainVhd(handler, 'parent.vhd', handler, 'child.vhd', true)
await createRandomFile(grandChildRandomFileName, mbOfGrandChildren)
await convertFromRawToVhd(grandChildRandomFileName, grandChildFileName)
await chainVhd(handler, 'child.vhd', handler, 'grandchild.vhd', true)
const parentVhd = new VhdFile(handler, 'parent.vhd')
await parentVhd.readHeaderAndFooter()
const childVhd = new VhdFile(handler, 'child.vhd')
await childVhd.readHeaderAndFooter()
const grandChildVhd = new VhdFile(handler, 'grandchild.vhd')
await grandChildVhd.readHeaderAndFooter()
await handler.writeFile(
`.parent.vhd.merge.json`,
JSON.stringify({
parent: {
header: parentVhd.header.checksum,
},
child: {
header: childVhd.header.checksum,
},
currentBlock: 1,
})
)
// should fail since the merge state file has only data of parent and child
await expect(
async () => await mergeVhdChain(handler, ['parent.vhd', 'child.vhd', 'grandchild.vhd'])
).rejects.toThrow()
// merge
await handler.unlink(`.parent.vhd.merge.json`)
await handler.writeFile(
`.parent.vhd.merge.json`,
JSON.stringify({
parent: {
header: parentVhd.header.checksum,
},
child: {
header: grandChildVhd.header.checksum,
},
currentBlock: 1,
childPath: ['child.vhd', 'grandchild.vhd'],
})
)
// it should succeed
await mergeVhdChain(handler, ['parent.vhd', 'child.vhd', 'grandchild.vhd'], { removeUnused: true })
expect(await fs.exists(`${tempDir}/parent.vhd`)).toBeFalsy()
expect(await fs.exists(`${tempDir}/child.vhd`)).toBeFalsy()
expect(await fs.exists(`${tempDir}/grandchild.vhd`)).toBeTruthy()
expect(await fs.exists(`${tempDir}/.parent.vhd.merge.json`)).toBeFalsy()
})
test('it merge multiple child in one pass ', async () => {
const mbOfFather = 8
const mbOfChildren = 6
const mbOfGrandChildren = 4
const parentRandomFileName = `${tempDir}/randomfile`
const childRandomFileName = `${tempDir}/small_randomfile`
const grandChildRandomFileName = `${tempDir}/another_small_randomfile`
const parentFileName = `${tempDir}/parent.vhd`
const childFileName = `${tempDir}/child.vhd`
const grandChildFileName = `${tempDir}/grandchild.vhd`
await createRandomFile(parentRandomFileName, mbOfFather)
await convertFromRawToVhd(parentRandomFileName, parentFileName)
await createRandomFile(childRandomFileName, mbOfChildren)
await convertFromRawToVhd(childRandomFileName, childFileName)
await chainVhd(handler, 'parent.vhd', handler, 'child.vhd', true)
await createRandomFile(grandChildRandomFileName, mbOfGrandChildren)
await convertFromRawToVhd(grandChildRandomFileName, grandChildFileName)
await chainVhd(handler, 'child.vhd', handler, 'grandchild.vhd', true)
// merge
await mergeVhdChain(handler, ['parent.vhd', 'child.vhd', 'grandchild.vhd'])
// check that vhd is still valid
await checkFile(grandChildFileName)
const parentVhd = new VhdFile(handler, 'grandchild.vhd')
await parentVhd.readHeaderAndFooter()
await parentVhd.readBlockAllocationTable()
let offset = 0
// check that the data are the same as source
for await (const block of parentVhd.blocks()) {
const blockContent = block.data
let file = parentRandomFileName
if (offset < mbOfGrandChildren * 1024 * 1024) {
file = grandChildRandomFileName
} else if (offset < mbOfChildren * 1024 * 1024) {
file = childRandomFileName
}
const buffer = Buffer.alloc(blockContent.length)
const fd = await fs.open(file, 'r')
await fs.read(fd, buffer, 0, buffer.length, offset)
expect(buffer.equals(blockContent)).toEqual(true)
offset += parentVhd.header.blockSize
}
})

View File

@@ -0,0 +1,128 @@
'use strict'
const { beforeEach, afterEach, describe, it } = require('test')
const { strict: assert } = require('assert')
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')
const { openVhd } = require('./index')
const { createRandomFile, convertFromRawToVhd, createRandomVhdDirectory } = require('./tests/utils')
const { VhdAbstract } = require('./Vhd/VhdAbstract')
let tempDir
describe('OpenVhd', async () => {
beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb))
})
afterEach(async () => {
await rimraf(tempDir)
})
it('opens a vhd file ( alias or not)', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
const vhd = yield openVhd(handler, 'randomfile.vhd')
assert.equal(vhd.header.cookie, 'cxsparse')
assert.equal(vhd.footer.cookie, 'conectix')
const aliasFileName = `out.alias.vhd`
await VhdAbstract.createAlias(handler, aliasFileName, 'randomfile.vhd')
const alias = yield openVhd(handler, aliasFileName)
assert.equal(alias.header.cookie, 'cxsparse')
assert.equal(alias.footer.cookie, 'conectix')
})
})
it('opens a vhd directory', async () => {
const initalSize = 4
const vhdDirectory = `${tempDir}/randomfile.dir`
await createRandomVhdDirectory(vhdDirectory, initalSize)
await Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
const vhd = yield openVhd(handler, 'randomfile.dir')
assert.equal(vhd.header.cookie, 'cxsparse')
assert.equal(vhd.footer.cookie, 'conectix')
const aliasFileName = `out.alias.vhd`
await VhdAbstract.createAlias(handler, aliasFileName, 'randomfile.dir')
const alias = yield openVhd(handler, aliasFileName)
assert.equal(alias.header.cookie, 'cxsparse')
assert.equal(alias.footer.cookie, 'conectix')
})
})
it('fails correctly when opening a broken vhd', async () => {
const initalSize = 4
// emtpy file
await assert.rejects(
Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
yield openVhd(handler, 'randomfile.vhd')
})
)
const rawFileName = `${tempDir}/randomfile.vhd`
await createRandomFile(rawFileName, initalSize)
// broken file
await assert.rejects(
Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
yield openVhd(handler, 'randomfile.vhd')
})
)
// empty dir
await fs.mkdir(`${tempDir}/dir.vhd`)
await assert.rejects(
Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
const vhd = yield openVhd(handler, 'dir.vhd')
await vhd.readBlockAllocationTable()
})
)
// 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 assert.rejects(
Disposable.use(async function* () {
const handler = yield getSyncedHandler({ url: `file://${tempDir}` })
const vhd = yield openVhd(handler, 'dir.vhd')
await vhd.readBlockAllocationTable()
})
)
await fs.rename(`${tempDir}/dir.vhd/moved`, `${tempDir}/dir.vhd/${target}`)
}
})
it('fails correctly when opening a vhdfile on an encrypted remote', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile.vhd`
await assert.rejects(
Disposable.use(async function* () {
const handler = yield getSyncedHandler({
url: `file://${tempDir}?encryptionKey="73c1838d7d8a6088ca2317fb5f29cd00"`,
})
await createRandomFile(rawFileName, initalSize)
yield openVhd(handler, 'randomfile.vhd')
})
)
})
})

View File

@@ -1,127 +0,0 @@
'use strict'
/* eslint-env jest */
const tmp = require('tmp')
const fs = require('node:fs/promises')
const { getSyncedHandler } = require('@xen-orchestra/fs')
const { Disposable, pFromCallback } = require('promise-toolbox')
const { rimraf } = require('rimraf')
const { openVhd } = require('./index')
const { createRandomFile, convertFromRawToVhd, createRandomVhdDirectory } = require('./tests/utils')
const { VhdAbstract } = require('./Vhd/VhdAbstract')
let tempDir
jest.setTimeout(60000)
beforeEach(async () => {
tempDir = await pFromCallback(cb => tmp.dir(cb))
})
afterEach(async () => {
await rimraf(tempDir)
})
test('It opens a vhd file ( alias or not)', async () => {
const initalSize = 4
const rawFileName = `${tempDir}/randomfile`
await createRandomFile(rawFileName, initalSize)
const vhdFileName = `${tempDir}/randomfile.vhd`
await convertFromRawToVhd(rawFileName, vhdFileName)
await Disposable.use(async function* () {
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 = `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')
})
})
test('It opens a vhd directory', async () => {
const initalSize = 4
const vhdDirectory = `${tempDir}/randomfile.dir`
await createRandomVhdDirectory(vhdDirectory, initalSize)
await Disposable.use(async function* () {
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 = `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()
})

View File

@@ -37,10 +37,12 @@
"execa": "^5.0.0",
"get-stream": "^6.0.0",
"rimraf": "^5.0.1",
"test": "^3.3.0",
"tmp": "^0.2.1"
},
"scripts": {
"postversion": "npm publish"
"postversion": "npm publish",
"test-integration": "node--test ./*.integ.js tests/*.integ.js Vhd/*.integ.js"
},
"author": {
"name": "Vates SAS",

View File

@@ -1,6 +1,7 @@
'use strict'
/* eslint-env jest */
const { beforeEach, afterEach, test } = require('test')
const { strict: assert } = require('assert')
const fs = require('fs-extra')
const tmp = require('tmp')
@@ -36,5 +37,5 @@ test('checkFile fails with unvalid VHD file', async () => {
const sizeToTruncateInByte = 250000
await fs.truncate(vhdFileName, sizeToTruncateInByte)
await expect(async () => await checkFile(vhdFileName)).rejects.toThrow()
await assert.rejects(async () => await checkFile(vhdFileName))
})