diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 38c4c0ad8..6adc5d116 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -7,6 +7,7 @@ - [Advanced] Configurable cookie validity (PR [#4059](https://github.com/vatesfr/xen-orchestra/pull/4059)) - [Plugins] Display number of installed plugins [#4008](https://github.com/vatesfr/xen-orchestra/issues/4008) (PR [#4050](https://github.com/vatesfr/xen-orchestra/pull/4050)) - [Continuous Replication] Opt-in mode to guess VHD size, should help with XenServer 7.1 CU2 and various `VDI_IO_ERROR` errors (PR [#3726](https://github.com/vatesfr/xen-orchestra/pull/3726)) +- [VM/Snapshots] Always delete broken quiesced snapshots [#4074](https://github.com/vatesfr/xen-orchestra/issues/4074) (PR [#4075](https://github.com/vatesfr/xen-orchestra/pull/4075)) ### Bug fixes diff --git a/packages/xo-server/src/xapi/index.js b/packages/xo-server/src/xapi/index.js index 5c181784e..0d9c46402 100644 --- a/packages/xo-server/src/xapi/index.js +++ b/packages/xo-server/src/xapi/index.js @@ -1567,47 +1567,28 @@ export default class Xapi extends XapiBase { }` ) + // see https://github.com/vatesfr/xen-orchestra/issues/4074 + const snapshotNameLabelPrefix = `Snapshot of ${vm.uuid} [` + ignoreErrors.call( + Promise.all( + vm.snapshots.map(async ref => { + const nameLabel = await this.getField('VM', ref, 'name_label') + if (nameLabel.startsWith(snapshotNameLabelPrefix)) { + return this._deleteVm(ref) + } + }) + ) + ) + let ref do { if (!vm.tags.includes('xo-disable-quiesce')) { try { - ref = await pRetry( - async bail => { - try { - return await this.callAsync( - $cancelToken, - 'VM.snapshot_with_quiesce', - vmRef, - nameLabel - ) - } catch (error) { - if (error?.code !== 'VM_SNAPSHOT_WITH_QUIESCE_FAILED') { - throw bail(error) - } - - // detect and remove new broken snapshots - // - // see https://github.com/vatesfr/xen-orchestra/issues/3936 - const prevSnapshotRefs = new Set(vm.snapshots) - const snapshotNameLabelPrefix = `Snapshot of ${vm.uuid} [` - vm.snapshots = await this.getField('VM', vmRef, 'snapshots') - const createdSnapshots = (await this.getRecords( - 'VM', - vm.snapshots.filter(_ => !prevSnapshotRefs.has(_)) - )).filter(_ => _.name_label.startsWith(snapshotNameLabelPrefix)) - - // be safe: only delete if there was a single match - if (createdSnapshots.length === 1) { - ignoreErrors.call(this._deleteVm(createdSnapshots[0])) - } - - throw error - } - }, - { - delay: 60e3, - tries: 3, - } + ref = await this.callAsync( + $cancelToken, + 'VM.snapshot_with_quiesce', + vmRef, + nameLabel ).then(extractOpaqueRef) ignoreErrors.call(this.call('VM.add_tags', ref, 'quiesce'))