feat(vhd-lib): add vhd synthetic class (#5990)
This commit is contained in:
committed by
GitHub
parent
8b0cee5e6f
commit
5c8ebce9eb
83
packages/vhd-lib/src/Vhd/VhdSynthetic.integ.spec.js
Normal file
83
packages/vhd-lib/src/Vhd/VhdSynthetic.integ.spec.js
Normal file
@@ -0,0 +1,83 @@
|
||||
/* eslint-env jest */
|
||||
|
||||
import rimraf from 'rimraf'
|
||||
import tmp from 'tmp'
|
||||
import { Disposable, pFromCallback } from 'promise-toolbox'
|
||||
import { getSyncedHandler } from '@xen-orchestra/fs'
|
||||
|
||||
import { SECTOR_SIZE, PLATFORM_W2KU } from '../_constants'
|
||||
import { createRandomFile, convertFromRawToVhd } from '../tests/utils'
|
||||
import { openVhd, chainVhd } from '..'
|
||||
import { VhdSynthetic } from './VhdSynthetic'
|
||||
|
||||
let tempDir = null
|
||||
|
||||
jest.setTimeout(60000)
|
||||
|
||||
beforeEach(async () => {
|
||||
tempDir = await pFromCallback(cb => tmp.dir(cb))
|
||||
})
|
||||
|
||||
afterEach(async () => {
|
||||
await pFromCallback(cb => rimraf(tempDir, cb))
|
||||
})
|
||||
|
||||
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_TYPE_DIFFERENCING
|
||||
await chainVhd(handler, bigVhdFileName, handler, smallVhdFileName, true)
|
||||
|
||||
const [smallVhd, bigVhd] = yield Disposable.all([
|
||||
openVhd(handler, smallVhdFileName),
|
||||
openVhd(handler, bigVhdFileName),
|
||||
])
|
||||
// add parent locato
|
||||
// this will also scramble the block inside the vhd files
|
||||
await bigVhd.writeParentLocator({
|
||||
id: 0,
|
||||
platformCode: PLATFORM_W2KU,
|
||||
data: Buffer.from('I am in the big one'),
|
||||
})
|
||||
const syntheticVhd = new VhdSynthetic([smallVhd, bigVhd])
|
||||
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).toEqual(buf)
|
||||
|
||||
content = (await syntheticVhd.readBlock(1)).data
|
||||
await handler.read(smallRawFileName, buf, buf.length)
|
||||
expect(content).toEqual(buf)
|
||||
|
||||
// the next one from big
|
||||
|
||||
content = (await syntheticVhd.readBlock(2)).data
|
||||
await handler.read(bigRawFileName, buf, buf.length * 2)
|
||||
expect(content).toEqual(buf)
|
||||
|
||||
content = (await syntheticVhd.readBlock(3)).data
|
||||
await handler.read(bigRawFileName, buf, buf.length * 3)
|
||||
expect(content).toEqual(buf)
|
||||
|
||||
// the parent locator should the one of the root vhd
|
||||
const parentLocator = await syntheticVhd.readParentLocator(0)
|
||||
expect(parentLocator.platformCode).toEqual(PLATFORM_W2KU)
|
||||
expect(Buffer.from(parentLocator.data, 'utf-8').toString()).toEqual('I am in the big one')
|
||||
})
|
||||
})
|
||||
88
packages/vhd-lib/src/Vhd/VhdSynthetic.js
Normal file
88
packages/vhd-lib/src/Vhd/VhdSynthetic.js
Normal file
@@ -0,0 +1,88 @@
|
||||
import { asyncMap } from '@xen-orchestra/async-map'
|
||||
import { VhdAbstract } from './VhdAbstract'
|
||||
import { DISK_TYPE_DIFFERENCING, FOOTER_SIZE, HEADER_SIZE } from '../_constants'
|
||||
|
||||
import assert from 'assert'
|
||||
|
||||
export class VhdSynthetic extends VhdAbstract {
|
||||
#vhds = []
|
||||
set header(_) {
|
||||
throw new Error('Header is read only for VhdSynthetic')
|
||||
}
|
||||
|
||||
get header() {
|
||||
// this the VHD we want to synthetize
|
||||
const vhd = this.#vhds[0]
|
||||
|
||||
// this is the root VHD
|
||||
const rootVhd = this.#vhds[this.#vhds.length - 1]
|
||||
|
||||
// data of our synthetic VHD
|
||||
// TODO: set parentLocatorEntry-s in header
|
||||
return {
|
||||
...vhd.header,
|
||||
tableOffset: FOOTER_SIZE + HEADER_SIZE,
|
||||
parentTimestamp: rootVhd.header.parentTimestamp,
|
||||
parentUnicodeName: rootVhd.header.parentUnicodeName,
|
||||
parentUuid: rootVhd.header.parentUuid,
|
||||
}
|
||||
}
|
||||
|
||||
set footer(_) {
|
||||
throw new Error('Footer is read only for VhdSynthetic')
|
||||
}
|
||||
|
||||
get footer() {
|
||||
// this is the root VHD
|
||||
const rootVhd = this.#vhds[this.#vhds.length - 1]
|
||||
return {
|
||||
...this.#vhds[0].footer,
|
||||
dataOffset: FOOTER_SIZE,
|
||||
diskType: rootVhd.footer.diskType,
|
||||
}
|
||||
}
|
||||
|
||||
static async open(vhds) {
|
||||
const vhd = new VhdSynthetic(vhds)
|
||||
return {
|
||||
dispose: () => {},
|
||||
value: vhd,
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @param {Array<VhdAbstract>} vhds the chain of Vhds used to compute this Vhd, from the deepest child (in position 0), to the root (in the last position)
|
||||
* only the last one can have any type. Other must have type DISK_TYPE_DIFFERENCING (delta)
|
||||
*/
|
||||
constructor(vhds) {
|
||||
assert(vhds.length > 0)
|
||||
super()
|
||||
this.#vhds = vhds
|
||||
}
|
||||
|
||||
async readBlockAllocationTable() {
|
||||
await asyncMap(this.#vhds, vhd => vhd.readBlockAllocationTable())
|
||||
}
|
||||
|
||||
containsBlock(blockId) {
|
||||
return this.#vhds.some(vhd => vhd.containsBlock(blockId))
|
||||
}
|
||||
|
||||
async readHeaderAndFooter() {
|
||||
await asyncMap(this.#vhds, vhd => vhd.readHeaderAndFooter())
|
||||
this.#vhds.forEach((vhd, index) => {
|
||||
if (index < this.#vhds.length) {
|
||||
assert.strictEqual(vhd.footer.diskType === DISK_TYPE_DIFFERENCING)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async readBlock(blockId, onlyBitmap = false) {
|
||||
const index = this.#vhds.findIndex(vhd => vhd.containsBlock(blockId))
|
||||
// only read the content of the first vhd containing this block
|
||||
return await this.#vhds[index].readBlock(blockId, onlyBitmap)
|
||||
}
|
||||
|
||||
_readParentLocatorData(id) {
|
||||
return this.#vhds[this.#vhds.length - 1]._readParentLocatorData(id)
|
||||
}
|
||||
}
|
||||
@@ -11,4 +11,5 @@ export { default as peekFooterFromVhdStream } from './peekFooterFromVhdStream'
|
||||
export { openVhd } from './openVhd'
|
||||
export { VhdDirectory } from './Vhd/VhdDirectory'
|
||||
export { VhdFile } from './Vhd/VhdFile'
|
||||
export { VhdSynthetic } from './Vhd/VhdSynthetic'
|
||||
export * as Constants from './_constants'
|
||||
|
||||
Reference in New Issue
Block a user