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
)
)
)