diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index be0b571be..721ad5cf8 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -7,6 +7,8 @@ > Users must be able to say: “Nice enhancement, I'm eager to test it” +- [Host/Advanced] Allow to force _Smart reboot_ if some resident VMs have the suspend operation blocked [Forum#7136](https://xcp-ng.org/forum/topic/7136/suspending-vms-during-host-reboot/23) (PR [#7025](https://github.com/vatesfr/xen-orchestra/pull/7025)) + ### Bug fixes > Users must be able to say: “I had this issue, happy to know it's fixed” @@ -29,5 +31,6 @@ - @xen-orchestra/xapi minor - xo-server minor +- xo-web minor diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index a1664cf41..609f8c9f8 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -963,9 +963,13 @@ const messages = { enableHostLabel: 'Enable', disableHostLabel: 'Disable', restartHostAgent: 'Restart toolstack', + smartRebootBypassCurrentVmCheck: + 'As the XOA is hosted on the host that is scheduled for a reboot, it will also be restarted. Consequently, XO won\'t be able to resume VMs, and VMs with the "Protect from accidental shutdown" option enabled will not have this option reactivated automatically.', smartRebootHostLabel: 'Smart reboot', smartRebootHostTooltip: 'Suspend resident VMs, reboot host and resume VMs automatically', forceRebootHostLabel: 'Force reboot', + forceSmartRebootHost: + 'Smart Reboot failed because {nVms, number} VM{nVms, plural, one {} other {s}} ha{nVms, plural, one {s} other {ve}} {nVms, plural, one {its} other {their}} Suspend operation blocked. Would you like to force?', rebootHostLabel: 'Reboot', noHostsAvailableErrorTitle: 'Error while restarting host', noHostsAvailableErrorMessage: diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index ecd5cc0c2..07ff07d71 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -16,6 +16,7 @@ import { incorrectState, noHostsAvailable, operationBlocked, + operationFailed, vmLacksFeature, } from 'xo-common/api-errors' @@ -821,42 +822,89 @@ export const setRemoteSyslogHost = (host, syslogDestination) => export const setRemoteSyslogHosts = (hosts, syslogDestination) => Promise.all(map(hosts, host => setRemoteSyslogHost(host, syslogDestination))) -export const restartHost = (host, force = false, suspendResidentVms = false) => - confirm({ +export const restartHost = async ( + host, + force = false, + suspendResidentVms = false, + bypassBlockedSuspend = false, + bypassCurrentVmCheck = false +) => { + await confirm({ title: _('restartHostModalTitle'), body: _('restartHostModalMessage'), - }).then( - () => - _call('host.restart', { id: resolveId(host), force, suspendResidentVms }) - .catch(async error => { - if ( - forbiddenOperation.is(error, { - reason: `A backup may run on the pool: ${host.$poolId}`, - }) || - forbiddenOperation.is(error, { - reason: `A backup is running on the pool: ${host.$poolId}`, - }) - ) { - await confirm({ - body: ( -

- {_('bypassBackupHostModalMessage')} -

- ), - title: _('restartHostModalTitle'), - }) - return _call('host.restart', { id: resolveId(host), force, suspendResidentVms, bypassBackupCheck: true }) - } - throw error - }) - .catch(error => { - if (noHostsAvailable.is(error)) { - alert(_('noHostsAvailableErrorTitle'), _('noHostsAvailableErrorMessage')) - } - throw error - }), - noop - ) + }) + return _restartHost({ host, force, suspendResidentVms, bypassBlockedSuspend, bypassCurrentVmCheck }) +} + +const _restartHost = async ({ host, ...opts }) => { + opts = { ...opts, id: resolveId(host) } + + try { + await _call('host.restart', opts) + } catch (error) { + if (cantSuspend(error)) { + await confirm({ + body: ( +

+ {_('forceSmartRebootHost', { nVms: error.data.actual.length })} +

+ ), + title: _('restartHostModalTitle'), + }) + return _restartHost({ ...opts, host, bypassBlockedSuspend: true }) + } + + if (xoaOnHost(error)) { + await confirm({ + body: ( +

+ {_('smartRebootBypassCurrentVmCheck')} +

+ ), + title: _('restartHostModalTitle'), + }) + return _restartHost({ ...opts, host, bypassCurrentVmCheck: true }) + } + + if (backupIsRunning(error, host.$poolId)) { + await confirm({ + body: ( +

+ {_('bypassBackupHostModalMessage')} +

+ ), + title: _('restartHostModalTitle'), + }) + return _restartHost({ ...opts, host, bypassBackupCheck: true }) + } + + if (noHostsAvailableErrCheck(error)) { + alert(_('noHostsAvailableErrorTitle'), _('noHostsAvailableErrorMessage')) + } + throw error + } +} + +// ---- Restart Host errors +const cantSuspend = err => + err !== undefined && + incorrectState.is(err, { + object: 'suspendBlocked', + }) +const xoaOnHost = err => + err !== undefined && + operationFailed.is(err, { + code: 'xoaOnHost', + }) +const backupIsRunning = (err, poolId) => + err !== undefined && + (forbiddenOperation.is(err, { + reason: `A backup may run on the pool: ${poolId}`, + }) || + forbiddenOperation.is(err, { + reason: `A backup is running on the pool: ${poolId}`, + })) +const noHostsAvailableErrCheck = err => err !== undefined && noHostsAvailable.is(err) export const restartHosts = (hosts, force = false) => { const nHosts = size(hosts) diff --git a/packages/xo-web/src/xo-app/host/tab-advanced.js b/packages/xo-web/src/xo-app/host/tab-advanced.js index a091898e7..5def6dcae 100644 --- a/packages/xo-web/src/xo-app/host/tab-advanced.js +++ b/packages/xo-web/src/xo-app/host/tab-advanced.js @@ -76,7 +76,7 @@ const downloadLogs = async uuid => { const forceReboot = host => restartHost(host, true) const smartReboot = ALLOW_SMART_REBOOT - ? host => restartHost(host, false, true) // don't force, suspend resident VMs + ? host => restartHost(host, false, true, false, false) // don't force, suspend resident VMs, don't bypass blocked suspend, don't bypass current VM check : () => {} const formatPack = ({ name, author, description, version }, key) => (