diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 04ccee7d64e..8467f3c2703 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -12,6 +12,7 @@ > Users must be able to say: “Nice enhancement, I'm eager to test it” - [REST API] Add backup repository information in the `/rest/v0/dashboard` endpoint (PR [#7882](https://github.com/vatesfr/xen-orchestra/pull/7882)) +- [VM/Disks] Allow user to delete snapshots before migrating VDI [#7275](https://github.com/vatesfr/xen-orchestra/issues/7275) (PR [#7903](https://github.com/vatesfr/xen-orchestra/pull/7903)) ### Bug fixes @@ -36,8 +37,9 @@ + - @xen-orchestra/proxy patch - xo-server minor -- xo-web patch +- xo-web minor diff --git a/packages/xo-server/src/api/vdi.mjs b/packages/xo-server/src/api/vdi.mjs index 2e588b1e6f1..ce56aa76c91 100644 --- a/packages/xo-server/src/api/vdi.mjs +++ b/packages/xo-server/src/api/vdi.mjs @@ -107,7 +107,7 @@ set.resolve = { // ------------------------------------------------------------------- -export async function migrate({ vdi, sr, resourceSet }) { +export async function migrate({ vdi, sr, removeSnapshotsBeforeMigrating, resourceSet }) { const xapi = this.getXapi(vdi) if (this.apiContext.permission !== 'admin') { @@ -118,13 +118,14 @@ export async function migrate({ vdi, sr, resourceSet }) { } } - await xapi.moveVdi(vdi._xapiRef, sr._xapiRef) + await xapi.moveVdi(vdi._xapiRef, sr._xapiRef, removeSnapshotsBeforeMigrating) return true } migrate.params = { id: { type: 'string' }, + removeSnapshotsBeforeMigrating: { type: 'boolean', default: false }, resourceSet: { type: 'string', optional: true }, sr_id: { type: 'string' }, } diff --git a/packages/xo-server/src/xapi/index.mjs b/packages/xo-server/src/xapi/index.mjs index 48c85b25c38..dd1211804e4 100644 --- a/packages/xo-server/src/xapi/index.mjs +++ b/packages/xo-server/src/xapi/index.mjs @@ -1035,7 +1035,7 @@ export default class Xapi extends XapiBase { return this.callAsync('VDI.clone', vdi.$ref) } - async moveVdi(vdiId, srId) { + async moveVdi(vdiId, srId, removeSnapshotsBeforeMigrating) { const vdi = this.getObject(vdiId) const sr = this.getObject(srId) @@ -1043,6 +1043,16 @@ export default class Xapi extends XapiBase { return vdi } + if (removeSnapshotsBeforeMigrating) { + for (const vdiRef of vdi.snapshots) { + const vdi = this.getObject(vdiRef) + for (const vbdRef of vdi.VBDs) { + const vbd = this.getObject(vbdRef) + await this.VM_destroy(vbd.VM) + } + } + } + log.debug(`Moving VDI ${vdi.name_label} from ${vdi.$SR.name_label} to ${sr.name_label}`) try { return this.barrier( diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index efe8ea57c16..96961807860 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -1328,6 +1328,7 @@ const messages = { vdiMigrateSelectSr: 'Destination SR:', vdiMigrateNoSr: 'No SR', vdiMigrateNoSrMessage: 'A target SR is required to migrate a VDI', + vdiMigrateWithoutSnapshots: 'Delete snapshots before migrate?', vdiForget: 'Forget', noControlDomainVdis: 'No VDIs attached to control domain', vbdBootableStatus: 'Boot flag', diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index 88f84b21639..917deb1f408 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -2480,9 +2480,10 @@ export const deleteOrphanedVdis = vdis => ), }).then(() => Promise.all(map(resolveIds(vdis), id => _call('vdi.delete', { id }))), noop) -export const migrateVdi = (vdi, sr, resourceSet) => +export const migrateVdi = (vdi, sr, resourceSet, removeSnapshotsBeforeMigrating) => _call('vdi.migrate', { id: resolveId(vdi), + removeSnapshotsBeforeMigrating, resourceSet, sr_id: resolveId(sr), }) diff --git a/packages/xo-web/src/common/xo/migrate-vdi-modal/index.js b/packages/xo-web/src/common/xo/migrate-vdi-modal/index.js index ebc9d2ae7fe..d296ca54f83 100644 --- a/packages/xo-web/src/common/xo/migrate-vdi-modal/index.js +++ b/packages/xo-web/src/common/xo/migrate-vdi-modal/index.js @@ -7,6 +7,7 @@ import { Container, Col } from 'grid' import { createCompare, createCompareContainers } from 'utils' import { createSelector } from 'selectors' import { SelectResourceSetsSr, SelectSr as SelectAnySr } from 'select-objects' +import { Toggle } from 'form' import { isSrIso, isSrShared, isSrWritable } from '../' @@ -14,6 +15,7 @@ const compareSrs = createCompare([isSrShared]) export default class MigrateVdiModalBody extends Component { static propTypes = { + nSnapshots: PropTypes.number, pool: PropTypes.string.isRequired, resourceSet: PropTypes.object, warningBeforeMigrate: PropTypes.func.isRequired, @@ -23,6 +25,10 @@ export default class MigrateVdiModalBody extends Component { return this.state } + state = { + removeSnapshotsBeforeMigrating: false, + } + _getCompareContainers = createSelector(() => this.props.pool, createCompareContainers) _getWarningBeforeMigrate = createSelector( @@ -38,7 +44,7 @@ export default class MigrateVdiModalBody extends Component { ) render() { - const { resourceSet } = this.props + const { nSnapshots, resourceSet } = this.props const warningBeforeMigrate = this._getWarningBeforeMigrate() const SelectSr = resourceSet !== undefined ? SelectResourceSetsSr : SelectAnySr return ( @@ -57,6 +63,16 @@ export default class MigrateVdiModalBody extends Component { /> + + {_('vdiMigrateWithoutSnapshots')} + + + + {warningBeforeMigrate !== null && ( {warningBeforeMigrate} diff --git a/packages/xo-web/src/xo-app/vm/tab-disks.js b/packages/xo-web/src/xo-app/vm/tab-disks.js index 806a2b6a9aa..9bc74e02f22 100644 --- a/packages/xo-web/src/xo-app/vm/tab-disks.js +++ b/packages/xo-web/src/xo-app/vm/tab-disks.js @@ -594,17 +594,26 @@ export default class TabDisks extends Component { }) _migrateVdis = vdis => { + const vdiSnapshots = [] + Object.keys(vdis).forEach(vdi => { + const nSnapshots = vdis[vdi].snapshots + if (nSnapshots) { + vdiSnapshots.push(...nSnapshots) + } + }) + const { resolvedResourceSet, vm } = this.props return confirm({ title: _('vdiMigrate'), body: ( ), - }).then(({ sr }) => { + }).then(({ sr, removeSnapshotsBeforeMigrating }) => { if (sr === undefined) { return error(_('vdiMigrateNoSr'), _('vdiMigrateNoSrMessage')) } @@ -614,7 +623,8 @@ export default class TabDisks extends Component { migrateVdi( vdi, sr, - getDefined(() => resolvedResourceSet.id) + getDefined(() => resolvedResourceSet.id), + removeSnapshotsBeforeMigrating ) ) )