This commit is contained in:
Florent BEAUCHAMP
2023-10-30 14:39:23 +01:00
parent 06cddc516d
commit 21e22626b6
4 changed files with 89 additions and 27 deletions

View File

@@ -14,7 +14,26 @@ export class ImportVmBackup {
this._xapi = xapi
}
async #detectBaseVdis(){
const vmUuid = this._metadata.vm.uuid
const vm = await this._xapi.getRecordByUuid('VM', vmUuid)
const disks = vm.$getDisks()
const snapshots = {}
console.log({disks})
for (const disk of Object.values(disks)){
console.log({snapshots: disk.snapshots})
for(const snapshotRef of disk.snapshots){
const snapshot = await this._xapi.getRecordByUuid('VDI', snapshotRef)
snapshots[snapshot.uuid] = disk.uuid
}
}
console.log({snapshots})
return snapshots
}
async run() {
console.log('RUN')
const adapter = this._adapter
const metadata = this._metadata
const isFull = metadata.mode === 'full'
@@ -22,20 +41,23 @@ export class ImportVmBackup {
const sizeContainer = { size: 0 }
let backup
if (isFull) {
backup = await adapter.readFullVmBackup(metadata)
watchStreamSize(backup, sizeContainer)
} else {
console.log('restore delta')
assert.strictEqual(metadata.mode, 'delta')
const ignoredVdis = new Set(
Object.entries(this._importIncrementalVmSettings.mapVdisSrs)
.filter(([_, srUuid]) => srUuid === null)
.map(([vdiUuid]) => vdiUuid)
)
const snapshotedVdis = {} // {vdiUuid => [snapshotsUuids]}
// VM => snapshots =>
backup = await adapter.readIncrementalVmBackup(metadata, ignoredVdis, { snapshotedVdis })
//const vdiSnap = await this._xapi.getRecord('VDI-snapshot','83c96977-9bc5-483d-b816-4c96622fb5e6')
//console.log({vdiSnap})
const baseVdis = this.#detectBaseVdis()
backup = await adapter.readIncrementalVmBackup(metadata, ignoredVdis, { baseVdis })
Object.values(backup.streams).forEach(stream => watchStreamSize(stream, sizeContainer))
}
@@ -51,7 +73,7 @@ export class ImportVmBackup {
? await xapi.VM_import(backup, srRef)
: await importIncrementalVm(backup, await xapi.getRecord('SR', srRef), {
...this._importIncrementalVmSettings,
detectBase: false,
baseVdis
})
await Promise.all([

View File

@@ -2,7 +2,7 @@ import { asyncEach } from '@vates/async-each'
import { asyncMap, asyncMapSettled } from '@xen-orchestra/async-map'
import { compose } from '@vates/compose'
import { createLogger } from '@xen-orchestra/log'
import { createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdDirectory, VhdSynthetic } from 'vhd-lib'
import { createVhdDirectoryFromStream, openVhd, VhdAbstract, VhdDirectory, VhdSynthetic ,Constants} from 'vhd-lib'
import { decorateMethodsWith } from '@vates/decorate-with'
import { deduped } from '@vates/disposable/deduped.js'
import { dirname, join, resolve } from 'node:path'
@@ -714,24 +714,61 @@ export class RemoteAdapter {
const synthetic = disposableSynthetic.value
await synthetic.readBlockAllocationTable()
let stream
// try to create a stream that will reuse any data already present on the host storage
// by looking for an existing snapshot matching one of the vhd in the chain
// and transfer only the differential
if (snapshotedVdis) {
// chain = []
// current = path
// find the child VDI of path
// more than one => break
// inexistant => break
// not a differential => break
// have snapshot => // must dig in the xapi to find how to check this
// descendant = new VhdSynthetic(chain)
// negativeVhd = new NegativeVhd(synthetic, descendant)
// stream = negative.stream()
// break
//
// // don't have a snapshot
// unshift this vhd at the beginning the chain
// current = child
//
// no negative stream : dispose all the vhd of the chain
try{
let vhdPaths = await handler.list(dirname(path), {filter: path=>path.endsWith('.vhd')})
stream = await Disposable.use(async function *(){
const vhdChilds = {}
const vhds = yield Disposable.all(vhdPaths.map(path => openVhd(handler, path, opts)))
for(const vhd of vhds){
vhdChilds[vhd.header.parentUuid] = vhdChilds[vhd.header.parentUuid] ?? []
vhdChilds[vhd.header.parentUuid].push(vhd)
}
let chain = []
let current = synthetic
// @todo : special case : we want to restore a vdi
// that still have its snapshot => nothing to transfer
while(current != undefined){
// find the child VDI of path
const childs = vhdChilds[current.footer.uuid]
// more than one => break
// inexistant => break
if(childs.length !== 1){
break
}
const child = childs[0]
// not a differential => we won't have a list of block changed
// no need to continue looking
if(child.footer.diskType !== Constants.DISK_TYPES.DIFFERENCING){
break
}
// we have a snapshot
if(snapshotedVdis[current.footer.uuid] !== undefined){
const descendants = VhdSynthetic.open(handler)
negativeVhd = new NegativeVhd(synthetic, descendants)
return negative.stream()
} else {
// continue to look into the chain
// hoping we'll found a match deeper
current = child
chain.unshift(current)
}
}
})
}catch(error){
warn("error while trying to reuse a snapshot, fallback to legacy restore", {error})
}
}
// fallback
if (stream === undefined) {

View File

@@ -143,6 +143,8 @@ export async function exportIncrementalVm(
)
}
// @todo movve this to incremental replication
async function detectBaseVdis(vmRecord, sr) {
let baseVm
const xapi = sr.$xapi
@@ -162,7 +164,7 @@ async function detectBaseVdis(vmRecord, sr) {
baseVm.$VBDs.forEach(vbd => {
const vdi = vbd.$VDI
if (vdi !== undefined) {
baseVdis[vbd.VDI] = vbd.$VDI
baseVdis[vdi.other_config[TAG_COPY_SRC]] = vbd.$VDI
}
})
return baseVdis
@@ -254,10 +256,11 @@ export const importIncrementalVm = defer(async function importIncrementalVm(
await asyncMap(Object.keys(vdiRecords), async vdiRef => {
const vdi = vdiRecords[vdiRef]
let newVdi
// @todo how to rewrite this condition when giving directly a baseVdi ?
const remoteBaseVdiUuid = detectBase && vdi.other_config[TAG_BASE_DELTA]
if (remoteBaseVdiUuid) {
const baseVdi = find(baseVdis, vdi => vdi.other_config[TAG_COPY_SRC] === remoteBaseVdiUuid)
const baseVdi = baseVdis[vdi.other_config[TAG_COPY_SRC]]
// @todo : should be an error only for detectBase
if (!baseVdi) {
throw new Error(`missing base VDI (copy of ${remoteBaseVdiUuid})`)
}

View File

@@ -37,7 +37,7 @@ function mapProperties(object, mapping) {
}
async function showDetails(handler, path) {
const vhd = new VhdFile(handler, resolve(path))
const {value: vhd} = await openVhd(handler, resolve(path))
try {
await vhd.readHeaderAndFooter()