feat(vhd-lib/mergeVhd): continuable (#5749)

This commit is contained in:
badrAZ 2021-04-30 09:18:21 +02:00 committed by GitHub
parent e6f8fd9234
commit aa4f1b834a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -3,10 +3,14 @@
import assert from 'assert'
import concurrency from 'limit-concurrency-decorator'
import noop from './_noop'
import { createLogger } from '@xen-orchestra/log'
import Vhd from './vhd'
import { basename, dirname } from 'path'
import { DISK_TYPE_DIFFERENCING, DISK_TYPE_DYNAMIC } from './_constants'
const { warn } = createLogger('vhd-lib:merge')
// Merge vhd child into vhd parent.
//
// TODO: rename the VHD file during the merge
@ -17,6 +21,8 @@ export default concurrency(2)(async function merge(
childPath,
{ onProgress = noop } = {}
) {
const mergeStatePath = dirname(parentPath) + '/' + '.' + basename(parentPath) + '.merge.json'
const parentFd = await parentHandler.openFile(parentPath, 'r+')
try {
const parentVhd = new Vhd(parentHandler, parentFd)
@ -24,31 +30,61 @@ export default concurrency(2)(async function merge(
try {
const childVhd = new Vhd(childHandler, childFd)
// Reading footer and header.
await Promise.all([parentVhd.readHeaderAndFooter(), childVhd.readHeaderAndFooter()])
let mergeState = await parentHandler.readFile(mergeStatePath).catch(error => {
if (error.code !== 'ENOENT') {
throw error
}
// no merge state in case of missing file
})
assert(childVhd.header.blockSize === parentVhd.header.blockSize)
// Reading footer and header.
await Promise.all([
parentVhd.readHeaderAndFooter(
// dont check VHD is complete if recovering a merge
mergeState === undefined
),
childVhd.readHeaderAndFooter(),
])
if (mergeState !== undefined) {
mergeState = JSON.parse(mergeState)
// ensure the correct merge will be continued
assert.strictEqual(parentVhd.header.checksum, mergeState.parent.header)
assert.strictEqual(childVhd.header.checksum, mergeState.child.header)
} else {
assert.strictEqual(childVhd.header.blockSize, parentVhd.header.blockSize)
const parentDiskType = parentVhd.footer.diskType
assert(parentDiskType === DISK_TYPE_DIFFERENCING || parentDiskType === DISK_TYPE_DYNAMIC)
assert.strictEqual(childVhd.footer.diskType, DISK_TYPE_DIFFERENCING)
}
// Read allocation table of child/parent.
await Promise.all([parentVhd.readBlockAllocationTable(), childVhd.readBlockAllocationTable()])
const { maxTableEntries } = childVhd.header
if (mergeState === undefined) {
await parentVhd.ensureBatSize(childVhd.header.maxTableEntries)
mergeState = {
child: { header: childVhd.header.checksum },
parent: { header: parentVhd.header.checksum },
currentBlock: 0,
mergedDataSize: 0,
}
// finds first allocated block for the 2 following loops
let firstBlock = 0
while (firstBlock < maxTableEntries && !childVhd.containsBlock(firstBlock)) {
++firstBlock
while (mergeState.currentBlock < maxTableEntries && !childVhd.containsBlock(mergeState.currentBlock)) {
++mergeState.currentBlock
}
}
// counts number of allocated blocks
let nBlocks = 0
for (let block = firstBlock; block < maxTableEntries; block++) {
for (let block = mergeState.currentBlock; block < maxTableEntries; block++) {
if (childVhd.containsBlock(block)) {
nBlocks += 1
}
@ -57,13 +93,14 @@ export default concurrency(2)(async function merge(
onProgress({ total: nBlocks, done: 0 })
// merges blocks
let mergedDataSize = 0
for (let i = 0, block = firstBlock; i < nBlocks; ++i, ++block) {
while (!childVhd.containsBlock(block)) {
++block
for (let i = 0; i < nBlocks; ++i, ++mergeState.currentBlock) {
while (!childVhd.containsBlock(mergeState.currentBlock)) {
++mergeState.currentBlock
}
mergedDataSize += await parentVhd.coalesceBlock(childVhd, block)
await parentHandler.writeFile(mergeStatePath, JSON.stringify(mergeState), { flags: 'w' }).catch(warn)
mergeState.mergedDataSize += await parentVhd.coalesceBlock(childVhd, mergeState.currentBlock)
onProgress({
total: nBlocks,
done: i + 1,
@ -83,11 +120,12 @@ export default concurrency(2)(async function merge(
// creation
await parentVhd.writeFooter()
return mergedDataSize
return mergeState.mergedDataSize
} finally {
await childHandler.closeFile(childFd)
}
} finally {
parentHandler.unlink(mergeStatePath).catch(warn)
await parentHandler.closeFile(parentFd)
}
})