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