feat(xo-server,xo-web): VM HA options (#2946)

Fixes #2917
This commit is contained in:
Rajaa.BARHTAOUI 2018-05-16 13:27:40 +02:00 committed by Pierre Donias
parent 458609ed2e
commit c3cb5a3221
6 changed files with 389 additions and 366 deletions

View File

@ -12,6 +12,10 @@ import { forEach, map, mapFilter, parseSize } from '../utils'
// ===================================================================
export function getHaValues () {
return ['best-effort', 'restart', '']
}
function checkPermissionOnSrs (vm, permission = 'operate') {
const permissions = []
forEach(vm.$VBDs, vbdId => {
@ -556,11 +560,11 @@ set.params = {
name_description: { type: 'string', optional: true },
// TODO: provides better filtering of values for HA possible values: "best-
// effort" meaning "try to restart this VM if possible but don't consider the
// Pool to be overcommitted if this is not possible"; "restart" meaning "this
// VM should be restarted"; "" meaning "do not try to restart this VM"
high_availability: { type: 'boolean', optional: true },
high_availability: {
optional: true,
pattern: new RegExp(`^(${getHaValues().join('|')})$`),
type: 'string',
},
// Number of virtual CPUs to allocate.
CPUs: { type: 'integer', optional: true },

View File

@ -292,8 +292,7 @@ const TRANSFORMS = {
}
})(),
// TODO: there is two possible value: "best-effort" and "restart"
high_availability: Boolean(obj.ha_restart_priority),
high_availability: obj.ha_restart_priority,
memory: (function () {
const dynamicMin = +obj.memory_dynamic_min

View File

@ -310,11 +310,7 @@ export default {
highAvailability: {
set (ha, vm) {
return this.call(
'VM.set_ha_restart_priority',
vm.$ref,
ha ? 'restart' : ''
)
return this.call('VM.set_ha_restart_priority', vm.$ref, ha)
},
},

View File

@ -956,6 +956,7 @@ const messages = {
vmCoresPerSocketIncorrectValue: 'Incorrect cores per socket value',
vmCoresPerSocketIncorrectValueSolution:
'Please change the selected value to fix it.',
vmHaDisabled: 'disabled',
vmMemoryLimitsLabel: 'Memory limits (min/max)',
vmMaxVcpus: 'vCPUs max:',
vmMaxRam: 'Memory max:',

View File

@ -1191,6 +1191,8 @@ export const editVm = (vm, props) =>
export const fetchVmStats = (vm, granularity) =>
_call('vm.stats', { id: resolveId(vm), granularity })
export const getVmsHaValues = () => _call('vm.getHaValues')
export const importVm = (file, type = 'xva', data = undefined, sr) => {
const { name } = file

View File

@ -29,6 +29,7 @@ import {
deleteVgpu,
deleteVm,
editVm,
getVmsHaValues,
isVmRunning,
recoveryStartVm,
restartVm,
@ -278,13 +279,8 @@ class CoresPerSocket extends Component {
}
}
export default connectStore(() => {
@connectStore(() => {
const getVgpus = createGetObjectsOfType('vgpu').pick((_, { vm }) => vm.$VGPUs)
const getVgpuTypes = createGetObjectsOfType('vgpuType').pick(
createSelector(getVgpus, vgpus => map(vgpus, 'vgpuType'))
)
const getGpuGroup = createGetObjectsOfType('gpuGroup').pick(
createSelector(getVgpus, vgpus => map(vgpus, 'gpuGroup'))
)
@ -293,367 +289,392 @@ export default connectStore(() => {
gpuGroup: getGpuGroup,
isAdmin,
vgpus: getVgpus,
vgpuTypes: getVgpuTypes,
}
})(({ container, gpuGroup, isAdmin, vgpus, vgpuTypes, vm }) => (
<Container>
<Row>
<Col className='text-xs-right'>
{vm.power_state === 'Running' && (
<span>
<TabButton
btnStyle='primary'
handler={suspendVm}
handlerParam={vm}
icon='vm-suspend'
labelId='suspendVmLabel'
/>
<TabButton
btnStyle='warning'
handler={forceReboot}
handlerParam={vm}
icon='vm-force-reboot'
labelId='forceRebootVmLabel'
/>
<TabButton
btnStyle='warning'
handler={forceShutdown}
handlerParam={vm}
icon='vm-force-shutdown'
labelId='forceShutdownVmLabel'
/>
</span>
)}
{vm.power_state === 'Halted' && (
<span>
<TabButton
btnStyle='primary'
handler={recoveryStartVm}
handlerParam={vm}
icon='vm-recovery-mode'
labelId='recoveryModeLabel'
/>
<TabButton
btnStyle='primary'
handler={fullCopy}
handlerParam={vm}
icon='vm-clone'
labelId='cloneVmLabel'
/>
})
export default class TabAdvanced extends Component {
componentDidMount () {
getVmsHaValues().then(vmsHaValues => this.setState({ vmsHaValues }))
}
render () {
const { container, isAdmin, vgpus, vm } = this.props
return (
<Container>
<Row>
<Col className='text-xs-right'>
{vm.power_state === 'Running' && (
<span>
<TabButton
btnStyle='primary'
handler={suspendVm}
handlerParam={vm}
icon='vm-suspend'
labelId='suspendVmLabel'
/>
<TabButton
btnStyle='warning'
handler={forceReboot}
handlerParam={vm}
icon='vm-force-reboot'
labelId='forceRebootVmLabel'
/>
<TabButton
btnStyle='warning'
handler={forceShutdown}
handlerParam={vm}
icon='vm-force-shutdown'
labelId='forceShutdownVmLabel'
/>
</span>
)}
{vm.power_state === 'Halted' && (
<span>
<TabButton
btnStyle='primary'
handler={recoveryStartVm}
handlerParam={vm}
icon='vm-recovery-mode'
labelId='recoveryModeLabel'
/>
<TabButton
btnStyle='primary'
handler={fullCopy}
handlerParam={vm}
icon='vm-clone'
labelId='cloneVmLabel'
/>
<TabButton
btnStyle='danger'
handler={convertVmToTemplate}
handlerParam={vm}
icon='vm-create-template'
labelId='vmConvertButton'
redirectOnSuccess='/'
/>
</span>
)}
{vm.power_state === 'Suspended' && (
<span>
<TabButton
btnStyle='primary'
handler={resumeVm}
handlerParam={vm}
icon='vm-start'
labelId='resumeVmLabel'
/>
<TabButton
btnStyle='warning'
handler={forceShutdown}
handlerParam={vm}
icon='vm-force-shutdown'
labelId='forceShutdownVmLabel'
/>
</span>
)}
<TabButton
btnStyle='danger'
handler={convertVmToTemplate}
handler={deleteVm}
handlerParam={vm}
icon='vm-create-template'
labelId='vmConvertButton'
redirectOnSuccess='/'
icon='vm-delete'
labelId='vmRemoveButton'
/>
</span>
)}
{vm.power_state === 'Suspended' && (
<span>
<TabButton
btnStyle='primary'
handler={resumeVm}
handlerParam={vm}
icon='vm-start'
labelId='resumeVmLabel'
/>
<TabButton
btnStyle='warning'
handler={forceShutdown}
handlerParam={vm}
icon='vm-force-shutdown'
labelId='forceShutdownVmLabel'
/>
</span>
)}
<TabButton
btnStyle='danger'
handler={deleteVm}
handlerParam={vm}
icon='vm-delete'
labelId='vmRemoveButton'
/>
</Col>
</Row>
<Row>
<Col>
<h3>{_('xenSettingsLabel')}</h3>
<table className='table'>
<tbody>
<tr>
<th>{_('uuid')}</th>
<Copiable tagName='td'>{vm.uuid}</Copiable>
</tr>
<tr>
<th>{_('virtualizationMode')}</th>
<td>
{vm.virtualizationMode === 'pv'
? _('paraVirtualizedMode')
: _('hardwareVirtualizedMode')}
</td>
</tr>
{vm.virtualizationMode === 'pv' && (
<tr>
<th>{_('pvArgsLabel')}</th>
<td>
<Text
value={vm.PV_args}
onChange={value => editVm(vm, { PV_args: value })}
/>
</td>
</tr>
)}
<tr>
<th>{_('cpuWeightLabel')}</th>
<td>
<Number
value={vm.cpuWeight == null ? null : vm.cpuWeight}
onChange={value => editVm(vm, { cpuWeight: value })}
nullable
>
{vm.cpuWeight == null
? _('defaultCpuWeight', { value: XEN_DEFAULT_CPU_WEIGHT })
: vm.cpuWeight}
</Number>
</td>
</tr>
<tr>
<th>{_('cpuCapLabel')}</th>
<td>
<Number
value={vm.cpuCap == null ? null : vm.cpuCap}
onChange={value => editVm(vm, { cpuCap: value })}
nullable
>
{vm.cpuCap == null
? _('defaultCpuCap', { value: XEN_DEFAULT_CPU_CAP })
: vm.cpuCap}
</Number>
</td>
</tr>
<tr>
<th>{_('autoPowerOn')}</th>
<td>
<Toggle
value={Boolean(vm.auto_poweron)}
onChange={value => editVm(vm, { auto_poweron: value })}
/>
</td>
</tr>
<tr>
<th>{_('windowsUpdateTools')}</th>
<td>
<Toggle
value={vm.hasVendorDevice}
onChange={value => editVm(vm, { hasVendorDevice: value })}
/>
</td>
</tr>
<tr>
<th>{_('ha')}</th>
<td>
<Toggle
value={vm.high_availability}
onChange={value => editVm(vm, { high_availability: value })}
/>
</td>
</tr>
<tr>
<th>{_('vmAffinityHost')}</th>
<td>
<AffinityHost vm={vm} />
</td>
</tr>
{vm.virtualizationMode === 'hvm' && (
<tr>
<th>{_('vmVgpus')}</th>
<td>
<Vgpus vgpus={vgpus} vm={vm} />
</td>
</tr>
)}
{vm.virtualizationMode === 'hvm' && (
<tr>
<th>{_('vmVga')}</th>
<td>
<Toggle
value={vm.vga === 'std'}
onChange={value =>
editVm(vm, { vga: value ? 'std' : 'cirrus' })
}
/>
</td>
</tr>
)}
{vm.vga === 'std' && (
<tr>
<th>{_('vmVideoram')}</th>
<td>
<select
className='form-control'
onChange={event =>
editVm(vm, { videoram: +getEventValue(event) })
}
value={vm.videoram}
>
{map(XEN_VIDEORAM_VALUES, val => (
<option key={val} value={val}>
{formatSize(val * 1048576)}
</option>
))}
</select>
</td>
</tr>
)}
</tbody>
</table>
<br />
<h3>{_('vmLimitsLabel')}</h3>
<table className='table table-hover'>
<tbody>
<tr>
<th>{_('vmCpuLimitsLabel')}</th>
<td>
<Number
value={vm.CPUs.number}
onChange={cpus => editVm(vm, { cpus })}
/>
/
{vm.power_state === 'Running' ? (
vm.CPUs.max
) : (
<Number
value={vm.CPUs.max}
onChange={cpusStaticMax => editVm(vm, { cpusStaticMax })}
/>
</Col>
</Row>
<Row>
<Col>
<h3>{_('xenSettingsLabel')}</h3>
<table className='table'>
<tbody>
<tr>
<th>{_('uuid')}</th>
<Copiable tagName='td'>{vm.uuid}</Copiable>
</tr>
<tr>
<th>{_('virtualizationMode')}</th>
<td>
{vm.virtualizationMode === 'pv'
? _('paraVirtualizedMode')
: _('hardwareVirtualizedMode')}
</td>
</tr>
{vm.virtualizationMode === 'pv' && (
<tr>
<th>{_('pvArgsLabel')}</th>
<td>
<Text
value={vm.PV_args}
onChange={value => editVm(vm, { PV_args: value })}
/>
</td>
</tr>
)}
</td>
</tr>
<tr>
<th>{_('vmCpuTopology')}</th>
<td>
<CoresPerSocket container={container} vm={vm} />
</td>
</tr>
<tr>
<th>{_('vmMemoryLimitsLabel')}</th>
<td>
<p>
Static: {formatSize(vm.memory.static[0])}/<Size
value={defined(vm.memory.static[1], null)}
onChange={memoryStaticMax =>
editVm(vm, { memoryStaticMax })
}
/>
</p>
<p>
Dynamic:{' '}
<Size
value={defined(vm.memory.dynamic[0], null)}
onChange={memoryMin => editVm(vm, { memoryMin })}
/>/<Size
value={defined(vm.memory.dynamic[1], null)}
onChange={memoryMax => editVm(vm, { memoryMax })}
/>
</p>
</td>
</tr>
</tbody>
</table>
<br />
<h3>{_('guestOsLabel')}</h3>
<table className='table table-hover'>
<tbody>
<tr>
<th>{_('xenToolsStatus')}</th>
<td>
{vm.xenTools && `${vm.xenTools.major}.${vm.xenTools.minor}`}
</td>
</tr>
<tr>
<th>{_('osName')}</th>
<td>
{isEmpty(vm.os_version) ? (
_('unknownOsName')
) : (
<span>
<Icon
className='text-info'
icon={osFamily(vm.os_version.distro)}
/>&nbsp;{vm.os_version.name}
</span>
)}
</td>
</tr>
<tr>
<th>{_('osKernel')}</th>
<td>
{(vm.os_version && vm.os_version.uname) || _('unknownOsKernel')}
</td>
</tr>
</tbody>
</table>
<br />
<h3>{_('miscLabel')}</h3>
<table className='table table-hover'>
<tbody>
<tr>
<th>{_('originalTemplate')}</th>
<td>
{vm.other.base_template_name
? vm.other.base_template_name
: _('unknownOriginalTemplate')}
</td>
</tr>
<tr>
<th>{_('resourceSet')}</th>
<td>
{isAdmin ? (
<div className='input-group'>
<SelectResourceSet
onChange={resourceSet =>
editVm(vm, {
resourceSet:
resourceSet != null ? resourceSet.id : resourceSet,
})
}
value={vm.resourceSet}
<tr>
<th>{_('cpuWeightLabel')}</th>
<td>
<Number
value={vm.cpuWeight == null ? null : vm.cpuWeight}
onChange={value => editVm(vm, { cpuWeight: value })}
nullable
>
{vm.cpuWeight == null
? _('defaultCpuWeight', {
value: XEN_DEFAULT_CPU_WEIGHT,
})
: vm.cpuWeight}
</Number>
</td>
</tr>
<tr>
<th>{_('cpuCapLabel')}</th>
<td>
<Number
value={vm.cpuCap == null ? null : vm.cpuCap}
onChange={value => editVm(vm, { cpuCap: value })}
nullable
>
{vm.cpuCap == null
? _('defaultCpuCap', { value: XEN_DEFAULT_CPU_CAP })
: vm.cpuCap}
</Number>
</td>
</tr>
<tr>
<th>{_('autoPowerOn')}</th>
<td>
<Toggle
value={Boolean(vm.auto_poweron)}
onChange={value => editVm(vm, { auto_poweron: value })}
/>
{vm.resourceSet !== undefined && (
<span className='input-group-btn'>
</td>
</tr>
<tr>
<th>{_('windowsUpdateTools')}</th>
<td>
<Toggle
value={vm.hasVendorDevice}
onChange={value => editVm(vm, { hasVendorDevice: value })}
/>
</td>
</tr>
<tr>
<th>{_('ha')}</th>
<td>
<select
className='form-control'
onChange={event =>
editVm(vm, { high_availability: getEventValue(event) })
}
value={vm.high_availability}
>
{map(this.state.vmsHaValues, vmsHaValue => (
<option key={vmsHaValue} value={vmsHaValue}>
{vmsHaValue === '' ? _('vmHaDisabled') : vmsHaValue}
</option>
))}
</select>
</td>
</tr>
<tr>
<th>{_('vmAffinityHost')}</th>
<td>
<AffinityHost vm={vm} />
</td>
</tr>
{vm.virtualizationMode === 'hvm' && (
<tr>
<th>{_('vmVgpus')}</th>
<td>
<Vgpus vgpus={vgpus} vm={vm} />
</td>
</tr>
)}
{vm.virtualizationMode === 'hvm' && (
<tr>
<th>{_('vmVga')}</th>
<td>
<Toggle
value={vm.vga === 'std'}
onChange={value =>
editVm(vm, { vga: value ? 'std' : 'cirrus' })
}
/>
</td>
</tr>
)}
{vm.vga === 'std' && (
<tr>
<th>{_('vmVideoram')}</th>
<td>
<select
className='form-control'
onChange={event =>
editVm(vm, { videoram: +getEventValue(event) })
}
value={vm.videoram}
>
{map(XEN_VIDEORAM_VALUES, val => (
<option key={val} value={val}>
{formatSize(val * 1048576)}
</option>
))}
</select>
</td>
</tr>
)}
</tbody>
</table>
<br />
<h3>{_('vmLimitsLabel')}</h3>
<table className='table table-hover'>
<tbody>
<tr>
<th>{_('vmCpuLimitsLabel')}</th>
<td>
<Number
value={vm.CPUs.number}
onChange={cpus => editVm(vm, { cpus })}
/>
/
{vm.power_state === 'Running' ? (
vm.CPUs.max
) : (
<Number
value={vm.CPUs.max}
onChange={cpusStaticMax =>
editVm(vm, { cpusStaticMax })
}
/>
)}
</td>
</tr>
<tr>
<th>{_('vmCpuTopology')}</th>
<td>
<CoresPerSocket container={container} vm={vm} />
</td>
</tr>
<tr>
<th>{_('vmMemoryLimitsLabel')}</th>
<td>
<p>
Static: {formatSize(vm.memory.static[0])}/<Size
value={defined(vm.memory.static[1], null)}
onChange={memoryStaticMax =>
editVm(vm, { memoryStaticMax })
}
/>
</p>
<p>
Dynamic:{' '}
<Size
value={defined(vm.memory.dynamic[0], null)}
onChange={memoryMin => editVm(vm, { memoryMin })}
/>/<Size
value={defined(vm.memory.dynamic[1], null)}
onChange={memoryMax => editVm(vm, { memoryMax })}
/>
</p>
</td>
</tr>
</tbody>
</table>
<br />
<h3>{_('guestOsLabel')}</h3>
<table className='table table-hover'>
<tbody>
<tr>
<th>{_('xenToolsStatus')}</th>
<td>
{vm.xenTools && `${vm.xenTools.major}.${vm.xenTools.minor}`}
</td>
</tr>
<tr>
<th>{_('osName')}</th>
<td>
{isEmpty(vm.os_version) ? (
_('unknownOsName')
) : (
<span>
<Icon
className='text-info'
icon={osFamily(vm.os_version.distro)}
/>&nbsp;{vm.os_version.name}
</span>
)}
</td>
</tr>
<tr>
<th>{_('osKernel')}</th>
<td>
{(vm.os_version && vm.os_version.uname) ||
_('unknownOsKernel')}
</td>
</tr>
</tbody>
</table>
<br />
<h3>{_('miscLabel')}</h3>
<table className='table table-hover'>
<tbody>
<tr>
<th>{_('originalTemplate')}</th>
<td>
{vm.other.base_template_name
? vm.other.base_template_name
: _('unknownOriginalTemplate')}
</td>
</tr>
<tr>
<th>{_('resourceSet')}</th>
<td>
{isAdmin ? (
<div className='input-group'>
<SelectResourceSet
onChange={resourceSet =>
editVm(vm, {
resourceSet:
resourceSet != null
? resourceSet.id
: resourceSet,
})
}
value={vm.resourceSet}
/>
{vm.resourceSet !== undefined && (
<span className='input-group-btn'>
<ActionButton
btnStyle='primary'
handler={shareVmProxy}
handlerParam={vm}
icon='vm-share'
style={SHARE_BUTTON_STYLE}
tooltip={_('vmShareButton')}
/>
</span>
)}
</div>
) : vm.resourceSet !== undefined ? (
<span>
<ResourceSetItem id={vm.resourceSet} />{' '}
<ActionButton
btnStyle='primary'
handler={shareVmProxy}
handlerParam={vm}
icon='vm-share'
style={SHARE_BUTTON_STYLE}
size='small'
tooltip={_('vmShareButton')}
/>
</span>
) : (
_('resourceSetNone')
)}
</div>
) : vm.resourceSet !== undefined ? (
<span>
<ResourceSetItem id={vm.resourceSet} />{' '}
<ActionButton
btnStyle='primary'
handler={shareVmProxy}
handlerParam={vm}
icon='vm-share'
size='small'
tooltip={_('vmShareButton')}
/>
</span>
) : (
_('resourceSetNone')
)}
</td>
</tr>
</tbody>
</table>
</Col>
</Row>
</Container>
))
</td>
</tr>
</tbody>
</table>
</Col>
</Row>
</Container>
)
}
}