diff --git a/@xen-orchestra/backups/RemoteAdapter.mjs b/@xen-orchestra/backups/RemoteAdapter.mjs index 993cc8038..8ab8bc1e8 100644 --- a/@xen-orchestra/backups/RemoteAdapter.mjs +++ b/@xen-orchestra/backups/RemoteAdapter.mjs @@ -191,13 +191,14 @@ export class RemoteAdapter { // check if we will be allowed to merge a a vhd created in this adapter // with the vhd at path `path` async isMergeableParent(packedParentUid, path) { - return await Disposable.use(openVhd(this.handler, path), vhd => { + return await Disposable.use(VhdSynthetic.fromVhdChain(this.handler, path), vhd => { // this baseUuid is not linked with this vhd if (!vhd.footer.uuid.equals(packedParentUid)) { return false } - const isVhdDirectory = vhd instanceof VhdDirectory + // check if all the chain is composed of vhd directory + const isVhdDirectory = vhd.checkVhdsClass(VhdDirectory) return isVhdDirectory ? this.useVhdDirectory() && this.#getCompressionType() === vhd.compressionType : !this.useVhdDirectory() diff --git a/@xen-orchestra/backups/_runners/_vmRunners/IncrementalXapi.mjs b/@xen-orchestra/backups/_runners/_vmRunners/IncrementalXapi.mjs index 994ab767c..b1ecf21b0 100644 --- a/@xen-orchestra/backups/_runners/_vmRunners/IncrementalXapi.mjs +++ b/@xen-orchestra/backups/_runners/_vmRunners/IncrementalXapi.mjs @@ -133,7 +133,7 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr ]) const srcVdi = srcVdis[snapshotOf] if (srcVdi !== undefined) { - baseUuidToSrcVdi.set(baseUuid, srcVdi) + baseUuidToSrcVdi.set(baseUuid, srcVdi.uuid) } else { debug('ignore snapshot VDI because no longer present on VM', { vdi: baseUuid, @@ -154,18 +154,18 @@ export const IncrementalXapi = class IncrementalXapiVmBackupRunner extends Abstr } const fullVdisRequired = new Set() - baseUuidToSrcVdi.forEach((srcVdi, baseUuid) => { + baseUuidToSrcVdi.forEach((srcVdiUuid, baseUuid) => { if (presentBaseVdis.has(baseUuid)) { debug('found base VDI', { base: baseUuid, - vdi: srcVdi.uuid, + vdi: srcVdiUuid, }) } else { debug('missing base VDI', { base: baseUuid, - vdi: srcVdi.uuid, + vdi: srcVdiUuid, }) - fullVdisRequired.add(srcVdi.uuid) + fullVdisRequired.add(srcVdiUuid) } }) diff --git a/@xen-orchestra/backups/_runners/_writers/IncrementalRemoteWriter.mjs b/@xen-orchestra/backups/_runners/_writers/IncrementalRemoteWriter.mjs index cb1885a86..0beb5f8f0 100644 --- a/@xen-orchestra/backups/_runners/_writers/IncrementalRemoteWriter.mjs +++ b/@xen-orchestra/backups/_runners/_writers/IncrementalRemoteWriter.mjs @@ -1,9 +1,8 @@ import assert from 'node:assert' import mapValues from 'lodash/mapValues.js' -import ignoreErrors from 'promise-toolbox/ignoreErrors' import { asyncEach } from '@vates/async-each' import { asyncMap } from '@xen-orchestra/async-map' -import { chainVhd, checkVhdChain, openVhd, VhdAbstract } from 'vhd-lib' +import { chainVhd, openVhd } from 'vhd-lib' import { createLogger } from '@xen-orchestra/log' import { decorateClass } from '@vates/decorate-with' import { defer } from 'golike-defer' @@ -23,42 +22,45 @@ import { Disposable } from 'promise-toolbox' const { warn } = createLogger('xo:backups:DeltaBackupWriter') export class IncrementalRemoteWriter extends MixinRemoteWriter(AbstractIncrementalWriter) { + #parentVdiPaths + #vhds async checkBaseVdis(baseUuidToSrcVdi) { + this.#parentVdiPaths = {} const { handler } = this._adapter const adapter = this._adapter const vdisDir = `${this._vmBackupDir}/vdis/${this._job.id}` - await asyncMap(baseUuidToSrcVdi, async ([baseUuid, srcVdi]) => { - let found = false + await asyncMap(baseUuidToSrcVdi, async ([baseUuid, srcVdiUuid]) => { + let parentDestPath + const vhdDir = `${vdisDir}/${srcVdiUuid}` try { - const vhds = await handler.list(`${vdisDir}/${srcVdi.uuid}`, { + const vhds = await handler.list(vhdDir, { filter: _ => _[0] !== '.' && _.endsWith('.vhd'), ignoreMissing: true, prependDir: true, }) const packedBaseUuid = packUuid(baseUuid) - await asyncMap(vhds, async path => { - try { - await checkVhdChain(handler, path) - // Warning, this should not be written as found = found || await adapter.isMergeableParent(packedBaseUuid, path) - // - // since all the checks of a path are done in parallel, found would be containing - // only the last answer of isMergeableParent which is probably not the right one - // this led to the support tickets https://help.vates.fr/#ticket/zoom/4751 , 4729, 4665 and 4300 + // the last one is probably the right one - const isMergeable = await adapter.isMergeableParent(packedBaseUuid, path) - found = found || isMergeable + for (let i = vhds.length - 1; i >= 0 && parentDestPath === undefined; i--) { + const path = vhds[i] + try { + if (await adapter.isMergeableParent(packedBaseUuid, path)) { + parentDestPath = path + } } catch (error) { warn('checkBaseVdis', { error }) - await ignoreErrors.call(VhdAbstract.unlink(handler, path)) } - }) + } } catch (error) { warn('checkBaseVdis', { error }) } - if (!found) { + // no usable parent => the runner will have to decide to fall back to a full or stop backup + if (parentDestPath === undefined) { baseUuidToSrcVdi.delete(baseUuid) + } else { + this.#parentVdiPaths[vhdDir] = parentDestPath } }) } diff --git a/@xen-orchestra/backups/_runners/_writers/IncrementalXapiWriter.mjs b/@xen-orchestra/backups/_runners/_writers/IncrementalXapiWriter.mjs index bdcfc6cd2..4b4421f88 100644 --- a/@xen-orchestra/backups/_runners/_writers/IncrementalXapiWriter.mjs +++ b/@xen-orchestra/backups/_runners/_writers/IncrementalXapiWriter.mjs @@ -1,3 +1,4 @@ +import assert from 'node:assert' import { asyncMap, asyncMapSettled } from '@xen-orchestra/async-map' import ignoreErrors from 'promise-toolbox/ignoreErrors' import { formatDateTime } from '@xen-orchestra/xapi' @@ -14,6 +15,7 @@ import find from 'lodash/find.js' export class IncrementalXapiWriter extends MixinXapiWriter(AbstractIncrementalWriter) { async checkBaseVdis(baseUuidToSrcVdi, baseVm) { + assert.notStrictEqual(baseVm, undefined) const sr = this._sr const replicatedVm = listReplicatedVms(sr.$xapi, this._job.id, sr.uuid, this._vmUuid).find( vm => vm.other_config[TAG_COPY_SRC] === baseVm.uuid