feat(vhd-lib): implement VhdNegative
it's a virtual Vhd that contains all the changes to be applied to reset a child to its parent value
This commit is contained in:
committed by
Julien Fontanet
parent
2748aea4e9
commit
408fc5af84
@@ -27,4 +27,9 @@
|
||||
|
||||
<!--packages-start-->
|
||||
|
||||
- @xen-orchestra/backups patch
|
||||
- vhd-lib minor
|
||||
- xo-server minor
|
||||
- xo-web minor
|
||||
|
||||
<!--packages-end-->
|
||||
|
||||
184
packages/vhd-lib/Vhd/VhdNegative.integ.js
Normal file
184
packages/vhd-lib/Vhd/VhdNegative.integ.js
Normal file
@@ -0,0 +1,184 @@
|
||||
'use strict'
|
||||
|
||||
const { VhdAbstract, VhdNegative } = require('..')
|
||||
|
||||
const { describe, it } = require('test')
|
||||
const assert = require('assert/strict')
|
||||
const { unpackHeader, unpackFooter } = require('./_utils')
|
||||
const { createHeader, createFooter } = require('../_createFooterHeader')
|
||||
const _computeGeometryForSize = require('../_computeGeometryForSize')
|
||||
const { FOOTER_SIZE, DISK_TYPES } = require('../_constants')
|
||||
|
||||
const VHD_BLOCK_LENGTH = 2 * 1024 * 1024
|
||||
class VhdMock extends VhdAbstract {
|
||||
#blockUsed
|
||||
#header
|
||||
#footer
|
||||
get header() {
|
||||
return this.#header
|
||||
}
|
||||
get footer() {
|
||||
return this.#footer
|
||||
}
|
||||
|
||||
constructor(header, footer, blockUsed = new Set()) {
|
||||
super()
|
||||
this.#header = header
|
||||
this.#footer = footer
|
||||
this.#blockUsed = blockUsed
|
||||
}
|
||||
containsBlock(blockId) {
|
||||
return this.#blockUsed.has(blockId)
|
||||
}
|
||||
readBlock(blockId, onlyBitmap = false) {
|
||||
const bitmap = Buffer.alloc(512, 255) // bitmap are full of bit 1
|
||||
|
||||
const data = Buffer.alloc(2 * 1024 * 1024, 0) // empty are full of bit 0
|
||||
data.writeUint8(blockId)
|
||||
return {
|
||||
id: blockId,
|
||||
bitmap,
|
||||
data,
|
||||
buffer: Buffer.concat([bitmap, data]),
|
||||
}
|
||||
}
|
||||
|
||||
readBlockAllocationTable() {}
|
||||
readHeaderAndFooter() {}
|
||||
_readParentLocatorData(id) {}
|
||||
}
|
||||
|
||||
describe('vhd negative', async () => {
|
||||
it(`throws when uid aren't chained `, () => {
|
||||
const length = 10e8
|
||||
|
||||
let header = unpackHeader(createHeader(length / VHD_BLOCK_LENGTH))
|
||||
const geometry = _computeGeometryForSize(length)
|
||||
let footer = unpackFooter(
|
||||
createFooter(length, Math.floor(Date.now() / 1000), geometry, FOOTER_SIZE, DISK_TYPES.DIFFERENCING)
|
||||
)
|
||||
const parent = new VhdMock(header, footer)
|
||||
|
||||
header = unpackHeader(createHeader(length / VHD_BLOCK_LENGTH))
|
||||
footer = unpackFooter(
|
||||
createFooter(length, Math.floor(Date.now() / 1000), geometry, FOOTER_SIZE, DISK_TYPES.DIFFERENCING)
|
||||
)
|
||||
|
||||
const child = new VhdMock(header, footer)
|
||||
assert.throws(() => new VhdNegative(parent, child), { message: 'NOT_CHAINED' })
|
||||
})
|
||||
|
||||
it('throws when size changed', () => {
|
||||
const childLength = 10e8
|
||||
const parentLength = 10e8 + 1
|
||||
|
||||
let header = unpackHeader(createHeader(parentLength / VHD_BLOCK_LENGTH))
|
||||
let geometry = _computeGeometryForSize(parentLength)
|
||||
let footer = unpackFooter(
|
||||
createFooter(parentLength, Math.floor(Date.now() / 1000), geometry, FOOTER_SIZE, DISK_TYPES.DIFFERENCING)
|
||||
)
|
||||
const parent = new VhdMock(header, footer)
|
||||
|
||||
header = unpackHeader(createHeader(childLength / VHD_BLOCK_LENGTH))
|
||||
geometry = _computeGeometryForSize(childLength)
|
||||
header.parentUuid = footer.uuid
|
||||
footer = unpackFooter(
|
||||
createFooter(childLength, Math.floor(Date.now() / 1000), geometry, FOOTER_SIZE, DISK_TYPES.DIFFERENCING)
|
||||
)
|
||||
const child = new VhdMock(header, footer)
|
||||
assert.throws(() => new VhdNegative(parent, child), { message: 'GEOMETRY_CHANGED' })
|
||||
})
|
||||
it('throws when child is not differencing', () => {
|
||||
const length = 10e8
|
||||
|
||||
let header = unpackHeader(createHeader(length / VHD_BLOCK_LENGTH))
|
||||
const geometry = _computeGeometryForSize(length)
|
||||
let footer = unpackFooter(
|
||||
createFooter(length, Math.floor(Date.now() / 1000), geometry, FOOTER_SIZE, DISK_TYPES.DIFFERENCING)
|
||||
)
|
||||
const parent = new VhdMock(header, footer)
|
||||
|
||||
header = unpackHeader(createHeader(length / VHD_BLOCK_LENGTH))
|
||||
header.parentUuid = footer.uuid
|
||||
footer = unpackFooter(
|
||||
createFooter(length, Math.floor(Date.now() / 1000), geometry, FOOTER_SIZE, DISK_TYPES.DYNAMIC)
|
||||
)
|
||||
|
||||
const child = new VhdMock(header, footer)
|
||||
assert.throws(() => new VhdNegative(parent, child), { message: 'CHILD_NOT_DIFFERENCING' })
|
||||
})
|
||||
|
||||
it(`throws when writing into vhd negative `, async () => {
|
||||
const length = 10e8
|
||||
|
||||
let header = unpackHeader(createHeader(length / VHD_BLOCK_LENGTH))
|
||||
const geometry = _computeGeometryForSize(length)
|
||||
let footer = unpackFooter(
|
||||
createFooter(length, Math.floor(Date.now() / 1000), geometry, FOOTER_SIZE, DISK_TYPES.DIFFERENCING)
|
||||
)
|
||||
const parent = new VhdMock(header, footer)
|
||||
const parentUuid = footer.uuid
|
||||
header = unpackHeader(createHeader(length / VHD_BLOCK_LENGTH))
|
||||
header.parentUuid = parentUuid
|
||||
footer = unpackFooter(
|
||||
createFooter(length, Math.floor(Date.now() / 1000), geometry, FOOTER_SIZE, DISK_TYPES.DIFFERENCING)
|
||||
)
|
||||
|
||||
const child = new VhdMock(header, footer)
|
||||
|
||||
const vhd = new VhdNegative(parent, child)
|
||||
|
||||
// await assert.rejects( ()=> vhd.writeFooter())
|
||||
assert.throws(() => vhd.writeHeader())
|
||||
assert.throws(() => vhd.writeBlockAllocationTable())
|
||||
assert.throws(() => vhd.writeEntireBlock())
|
||||
assert.throws(() => vhd.mergeBlock(), { message: `can't coalesce block into a vhd negative` })
|
||||
})
|
||||
|
||||
it('normal case', async () => {
|
||||
const length = 10e8
|
||||
|
||||
let header = unpackHeader(createHeader(length / VHD_BLOCK_LENGTH))
|
||||
let geometry = _computeGeometryForSize(length)
|
||||
let footer = unpackFooter(
|
||||
createFooter(length, Math.floor(Date.now() / 1000), geometry, FOOTER_SIZE, DISK_TYPES.DYNAMIC)
|
||||
)
|
||||
const parent = new VhdMock(header, footer, new Set([1, 3]))
|
||||
const parentUuid = footer.uuid
|
||||
header = unpackHeader(createHeader(length / VHD_BLOCK_LENGTH))
|
||||
header.parentUuid = parentUuid
|
||||
geometry = _computeGeometryForSize(length)
|
||||
footer = unpackFooter(
|
||||
createFooter(length, Math.floor(Date.now() / 1000), geometry, FOOTER_SIZE, DISK_TYPES.DIFFERENCING)
|
||||
)
|
||||
|
||||
const childUuid = footer.uuid
|
||||
const child = new VhdMock(header, footer, new Set([2, 3]))
|
||||
|
||||
const vhd = new VhdNegative(parent, child)
|
||||
assert.equal(vhd.header.parentUuid.equals(childUuid), true)
|
||||
assert.equal(vhd.footer.diskType, DISK_TYPES.DIFFERENCING)
|
||||
await vhd.readBlockAllocationTable()
|
||||
await vhd.readHeaderAndFooter()
|
||||
await vhd.readParentLocator(0)
|
||||
assert.equal(vhd.header.parentUuid, childUuid)
|
||||
assert.equal(vhd.footer.diskType, DISK_TYPES.DIFFERENCING)
|
||||
assert.equal(vhd.containsBlock(1), false)
|
||||
assert.equal(vhd.containsBlock(2), true)
|
||||
assert.equal(vhd.containsBlock(3), true)
|
||||
assert.equal(vhd.containsBlock(4), false)
|
||||
|
||||
const expected = [0, 1, 0, 3, 0]
|
||||
const expectedBitmap = Buffer.alloc(512, 255) // bitmap must always be full of bit 1
|
||||
for (let index = 0; index < 5; index++) {
|
||||
if (vhd.containsBlock(index)) {
|
||||
const { id, data, bitmap } = await vhd.readBlock(index)
|
||||
assert.equal(index, id)
|
||||
assert.equal(expectedBitmap.equals(bitmap), true)
|
||||
assert.equal(data.readUInt8(0), expected[index])
|
||||
} else {
|
||||
assert.equal([2, 3].includes(index), false)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
84
packages/vhd-lib/Vhd/VhdNegative.js
Normal file
84
packages/vhd-lib/Vhd/VhdNegative.js
Normal file
@@ -0,0 +1,84 @@
|
||||
'use strict'
|
||||
|
||||
const UUID = require('uuid')
|
||||
const { DISK_TYPES } = require('../_constants')
|
||||
const { VhdAbstract } = require('./VhdAbstract')
|
||||
const { computeBlockBitmapSize } = require('./_utils')
|
||||
const assert = require('node:assert')
|
||||
/**
|
||||
* Build an incremental VHD which can be applied to a child to revert to the state of its parent.
|
||||
* @param {*} parent
|
||||
* @param {*} descendant
|
||||
*/
|
||||
|
||||
class VhdNegative extends VhdAbstract {
|
||||
#parent
|
||||
#child
|
||||
|
||||
get header() {
|
||||
// we want to have parent => child => negative
|
||||
// where => means " is the parent of "
|
||||
return {
|
||||
...this.#parent.header,
|
||||
parentUuid: this.#child.footer.uuid,
|
||||
}
|
||||
}
|
||||
|
||||
get footer() {
|
||||
// by construct a negative vhd is differencing disk
|
||||
return {
|
||||
...this.#parent.footer,
|
||||
diskType: DISK_TYPES.DIFFERENCING,
|
||||
}
|
||||
}
|
||||
|
||||
constructor(parent, child) {
|
||||
super()
|
||||
this.#parent = parent
|
||||
this.#child = child
|
||||
|
||||
assert.strictEqual(UUID.stringify(child.header.parentUuid), UUID.stringify(parent.footer.uuid), 'NOT_CHAINED')
|
||||
assert.strictEqual(child.footer.diskType, DISK_TYPES.DIFFERENCING, 'CHILD_NOT_DIFFERENCING')
|
||||
// we don't want to handle alignment and missing block for now
|
||||
// last block may contains partly empty data when changing size
|
||||
assert.strictEqual(child.footer.currentSize, parent.footer.currentSize, 'GEOMETRY_CHANGED')
|
||||
}
|
||||
|
||||
async readBlockAllocationTable() {
|
||||
return Promise.all([this.#parent.readBlockAllocationTable(), this.#child.readBlockAllocationTable()])
|
||||
}
|
||||
|
||||
containsBlock(blockId) {
|
||||
return this.#child.containsBlock(blockId)
|
||||
}
|
||||
|
||||
async readHeaderAndFooter() {
|
||||
return Promise.all([this.#parent.readHeaderAndFooter(), this.#child.readHeaderAndFooter()])
|
||||
}
|
||||
|
||||
async readBlock(blockId, onlyBitmap = false) {
|
||||
// only read the content of the first vhd containing this block
|
||||
if (this.#parent.containsBlock(blockId)) {
|
||||
return this.#parent.readBlock(blockId, onlyBitmap)
|
||||
}
|
||||
|
||||
const bitmap = Buffer.alloc(computeBlockBitmapSize(this.header.blockSize), 255) // bitmap are full of bit 1
|
||||
const data = Buffer.alloc(this.header.blockSize, 0) // empty are full of bit 0
|
||||
return {
|
||||
id: blockId,
|
||||
bitmap,
|
||||
data,
|
||||
buffer: Buffer.concat([bitmap, data]),
|
||||
}
|
||||
}
|
||||
|
||||
mergeBlock(child, blockId) {
|
||||
throw new Error(`can't coalesce block into a vhd negative`)
|
||||
}
|
||||
|
||||
_readParentLocatorData(id) {
|
||||
return this.#parent._readParentLocatorData(id)
|
||||
}
|
||||
}
|
||||
|
||||
exports.VhdNegative = VhdNegative
|
||||
@@ -13,4 +13,5 @@ exports.VhdAbstract = require('./Vhd/VhdAbstract').VhdAbstract
|
||||
exports.VhdDirectory = require('./Vhd/VhdDirectory').VhdDirectory
|
||||
exports.VhdFile = require('./Vhd/VhdFile').VhdFile
|
||||
exports.VhdSynthetic = require('./Vhd/VhdSynthetic').VhdSynthetic
|
||||
exports.VhdNegative = require('./Vhd/VhdNegative').VhdNegative
|
||||
exports.Constants = require('./_constants')
|
||||
|
||||
Reference in New Issue
Block a user