test(vhd-lib): from Jest to test
This commit is contained in:
273
packages/vhd-lib/Vhd/VhdAbstract.integ.js
Normal file
273
packages/vhd-lib/Vhd/VhdAbstract.integ.js
Normal 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
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
})
|
||||
126
packages/vhd-lib/Vhd/VhdDirectory.integ.js
Normal file
126
packages/vhd-lib/Vhd/VhdDirectory.integ.js
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
238
packages/vhd-lib/Vhd/VhdFile.integ.js
Normal file
238
packages/vhd-lib/Vhd/VhdFile.integ.js
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
89
packages/vhd-lib/Vhd/VhdSynthetic.integ.js
Normal file
89
packages/vhd-lib/Vhd/VhdSynthetic.integ.js
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
/* eslint-env jest */
|
||||
const { test } = require('test')
|
||||
|
||||
const { createFooter } = require('./_createFooterHeader')
|
||||
|
||||
test('createFooter() does not crash', () => {
|
||||
77
packages/vhd-lib/aliases.integ.js
Normal file
77
packages/vhd-lib/aliases.integ.js
Normal 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)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
@@ -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])
|
||||
})
|
||||
})
|
||||
365
packages/vhd-lib/merge.integ.js
Normal file
365
packages/vhd-lib/merge.integ.js
Normal 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
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -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
|
||||
}
|
||||
})
|
||||
128
packages/vhd-lib/openVhd.integ.js
Normal file
128
packages/vhd-lib/openVhd.integ.js
Normal 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')
|
||||
})
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -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()
|
||||
})
|
||||
@@ -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",
|
||||
|
||||
@@ -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))
|
||||
})
|
||||
Reference in New Issue
Block a user