diff --git a/@xen-orchestra/xapi/vm.mjs b/@xen-orchestra/xapi/vm.mjs
index 817dd52d7fe..bdc8a2a3d89 100644
--- a/@xen-orchestra/xapi/vm.mjs
+++ b/@xen-orchestra/xapi/vm.mjs
@@ -599,6 +599,33 @@ class Vm {
}
}
+ async coalesceLeaf($defer, vmRef) {
+ try {
+ await this.callAsync('VM.suspend', vmRef)
+ $defer(() => this.callAsync('VM.resume', vmRef, false, true))
+ } catch (error) {
+ if (error.code !== 'VM_BAD_POWER_STATE') {
+ throw error
+ }
+
+ const powerState = error.params[2].toLowerCase()
+ if (powerState !== 'halted' && powerState !== 'suspended') {
+ throw error
+ }
+ }
+
+ // plugin doc: https://docs.xenserver.com/en-us/xenserver/8/storage/manage.html#reclaim-space-by-using-the-offline-coalesce-tool
+ // result can be: `Success` or `VM has no leaf-coalesceable VDIs`
+ // https://github.com/xapi-project/sm/blob/eb292457c5fd5f00f6fc82454a915068ab15aa6f/drivers/coalesce-leaf#L48
+ const result = await this.callAsync('host.call_plugin', this.pool.master, 'coalesce-leaf', 'leaf-coalesce', {
+ vm_uuid: await this.getField('VM', vmRef, 'uuid'),
+ })
+
+ if (result.toLowerCase() !== 'success') {
+ throw new Error(result)
+ }
+ }
+
async snapshot(
$defer,
vmRef,
@@ -708,5 +735,6 @@ decorateClass(Vm, {
checkpoint: defer,
create: defer,
export: defer,
+ coalesceLeaf: defer,
snapshot: defer,
})
diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md
index 4f694647f42..f0adf8714e7 100644
--- a/CHANGELOG.unreleased.md
+++ b/CHANGELOG.unreleased.md
@@ -14,6 +14,7 @@
- [i18n] Add Persian translation (based on the contribution made by [@Jokar-xen](https://github.com/Jokar-xen)) (PR [#7775](https://github.com/vatesfr/xen-orchestra/pull/7775))
- [i18n] Improve Russian translation (Thanks [@TristisOris](https://github.com/TristisOris)!) (PR [#7807](https://github.com/vatesfr/xen-orchestra/pull/7807))
- [REST API] Expose XO6 dashboard informations at the `/rest/v0/dashboard` endpoint (PR [#7823](https://github.com/vatesfr/xen-orchestra/pull/7823))
+- [VM/Advanced] Possibility to manually [_Coalesce Leaf_](https://docs.xenserver.com/en-us/xenserver/8/storage/manage.html#reclaim-space-by-using-the-offline-coalesce-tool) [#7757](https://github.com/vatesfr/xen-orchestra/issues/7757) (PR [#7810](https://github.com/vatesfr/xen-orchestra/pull/7810))
### Bug fixes
@@ -35,6 +36,7 @@
+- @xen-orchestra/xapi minor
- xo-server minor
- xo-web minor
diff --git a/packages/xo-server/src/api/vm.mjs b/packages/xo-server/src/api/vm.mjs
index 0232e26ed26..b175a18f07b 100644
--- a/packages/xo-server/src/api/vm.mjs
+++ b/packages/xo-server/src/api/vm.mjs
@@ -1826,3 +1826,15 @@ deleteVgpu.params = {
deleteVgpu.resolve = {
vgpu: ['vgpu', 'vgpu', ''],
}
+
+// -------------------------------------------------------------------
+
+export async function coalesceLeaf({ vm }) {
+ await this.getXapi(vm).VM_coalesceLeaf(vm._xapiRef)
+}
+coalesceLeaf.params = {
+ id: { type: 'string' },
+}
+coalesceLeaf.resolve = {
+ vm: ['id', 'VM', 'administrate'],
+}
diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js
index 4ca95daa240..358f8e136f4 100644
--- a/packages/xo-web/src/common/intl/messages.js
+++ b/packages/xo-web/src/common/intl/messages.js
@@ -1452,6 +1452,9 @@ const messages = {
'If the VTPM is in use, removing it will result in a dangerous data loss. Are you sure you want to remove the VTPM?',
infoUnknownPciOnNonRunningVm:
"When a VM is offline, it's not attached to any host, and therefore, it's impossible to determine the associated PCI devices, as it depends on the hardware environment in which it would be deployed.",
+ coalesceLeaf: 'Coalesce leaf',
+ coalesceLeafSuccess: 'Coalesce leaf success',
+ coalesceLeafSuspendVm: 'This will suspend the VM during the operation. Do you want to continue?',
poolAutoPoweronDisabled: 'Auto power on is disabled at pool level, click to fix automatically.',
vmRemoveButton: 'Remove',
vmConvertToTemplateButton: 'Convert to template',
diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js
index 5ded951fce0..672288f38d1 100644
--- a/packages/xo-web/src/common/xo/index.js
+++ b/packages/xo-web/src/common/xo/index.js
@@ -2004,6 +2004,18 @@ export const deleteVms = async vms => {
}
}
+export const coalesceLeafVm = async vm => {
+ if (vm.power_state !== 'Halted' && vm.power_state !== 'Suspended') {
+ await confirm({
+ title: _('coalesceLeaf'),
+ body: _('coalesceLeafSuspendVm'),
+ })
+ }
+ await _call('vm.coalesceLeaf', { id: resolveId(vm) })
+
+ success(_('coalesceLeaf'), _('coalesceLeafSuccess'))
+}
+
export const importBackup = ({ remote, file, sr }) => _call('vm.importBackup', resolveIds({ remote, file, sr }))
export const importDeltaBackup = ({ remote, file, sr, mapVdisSrs }) =>
diff --git a/packages/xo-web/src/icons.scss b/packages/xo-web/src/icons.scss
index af5f6a07c5a..56bc7981d0e 100644
--- a/packages/xo-web/src/icons.scss
+++ b/packages/xo-web/src/icons.scss
@@ -511,6 +511,11 @@
@extend .fa;
@extend .fa-fire;
}
+
+ &-coalesce-leaf {
+ @extend .fa;
+ @extend .fa-compress;
+ }
}
// Generic states
diff --git a/packages/xo-web/src/xo-app/vm/tab-advanced.js b/packages/xo-web/src/xo-app/vm/tab-advanced.js
index 7b6cd177d92..930c8087e4a 100644
--- a/packages/xo-web/src/xo-app/vm/tab-advanced.js
+++ b/packages/xo-web/src/xo-app/vm/tab-advanced.js
@@ -41,6 +41,7 @@ import {
getVmsHaValues,
isPciPassthroughAvailable,
isVmRunning,
+ coalesceLeafVm,
pauseVm,
recoveryStartVm,
removeAcl,
@@ -698,6 +699,13 @@ export default class TabAdvanced extends Component {
icon='vm-suspend'
labelId='suspendVmLabel'
/>
+
+
)}
{vm.power_state === 'Suspended' && (
@@ -756,6 +771,13 @@ export default class TabAdvanced extends Component {
icon='vm-start'
labelId='resumeVmLabel'
/>
+