From 8971d218bba515ac9dd062fe7a8be8f1576d3c31 Mon Sep 17 00:00:00 2001 From: badrAZ Date: Tue, 13 Feb 2018 15:24:20 +0100 Subject: [PATCH] feat: share a vm in a resource set (#2611) Fixes #2589 --- packages/xo-server/src/api/vm.js | 8 +- .../xo-server/src/xo-mixins/resource-sets.js | 16 +- packages/xo-web/src/common/intl/messages.js | 4 + packages/xo-web/src/common/xo/index.js | 9 + packages/xo-web/src/icons.scss | 4 + packages/xo-web/src/xo-app/vm/tab-advanced.js | 669 ++++++++++-------- 6 files changed, 392 insertions(+), 318 deletions(-) diff --git a/packages/xo-server/src/api/vm.js b/packages/xo-server/src/api/vm.js index 4aa24d6a0..7df003f28 100644 --- a/packages/xo-server/src/api/vm.js +++ b/packages/xo-server/src/api/vm.js @@ -500,7 +500,6 @@ export async function set (params) { const vmId = VM._xapiId const resourceSetId = extract(params, 'resourceSet') - if (resourceSetId !== undefined) { if (this.user.permission !== 'admin') { throw unauthorized() @@ -509,6 +508,11 @@ export async function set (params) { await this.setVmResourceSet(vmId, resourceSetId) } + const share = extract(params, 'share') + if (share) { + await this.shareVmResourceSet(vmId) + } + return xapi.editVm(vmId, params, async (limits, vm) => { const resourceSet = xapi.xo.getData(vm, 'resourceSet') @@ -580,6 +584,8 @@ set.params = { // Move the vm In to/Out of Self Service resourceSet: { type: ['string', 'null'], optional: true }, + + share: { type: 'boolean', optional: true }, } set.resolve = { diff --git a/packages/xo-server/src/xo-mixins/resource-sets.js b/packages/xo-server/src/xo-mixins/resource-sets.js index a5dec8e9a..bfe01c878 100644 --- a/packages/xo-server/src/xo-mixins/resource-sets.js +++ b/packages/xo-server/src/xo-mixins/resource-sets.js @@ -391,10 +391,18 @@ export default class { await this._xo.removeAclsForObject(vmId) } if (resourceSetId != null) { - const { subjects } = await this.getResourceSet(resourceSetId) - await asyncMap(subjects, subject => - this._xo.addAcl(subject, vmId, 'admin') - ) + await this.shareVmResourceSet(vmId) } } + + async shareVmResourceSet (vmId) { + const xapi = this._xo.getXapi(vmId) + const resourceSetId = xapi.xo.getData(vmId, 'resourceSet') + if (resourceSetId === undefined) { + throw new Error('the vm is not in a resource set') + } + + const { subjects } = await this.getResourceSet(resourceSetId) + await asyncMap(subjects, subject => this._xo.addAcl(subject, vmId, 'admin')) + } } diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index da95666ca..8880ba7c3 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -844,6 +844,7 @@ const messages = { // ----- VM advanced tab ----- vmRemoveButton: 'Remove', vmConvertButton: 'Convert', + vmShareButton: 'Share', xenSettingsLabel: 'Xen settings', guestOsLabel: 'Guest OS', miscLabel: 'Misc', @@ -1252,6 +1253,9 @@ const messages = { deleteRemotesModalMessage: 'Are you sure you want to delete {nRemotes, number} remote{nRemotes, plural, one {} other {s}}?', revertVmModalTitle: 'Revert your VM', + shareVmInResourceSetModalTitle: 'Share your VM', + shareVmInResourceSetModalMessage: + 'This VM will be shared with all the members of the self-service {self}. Are you sure?', deleteVifsModalTitle: 'Delete VIF{nVifs, plural, one {} other {s}}', deleteVifsModalMessage: 'Are you sure you want to delete {nVifs, number} VIF{nVifs, plural, one {} other {s}}?', diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index 345b1704a..046cc8e0e 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -24,6 +24,7 @@ import { forbiddenOperation, noHostsAvailable } from 'xo-common/api-errors' import _ from '../intl' import invoke from '../invoke' import logError from '../log-error' +import renderXoItem from '../render-xo-item' import store from 'store' import { alert, chooseAction, confirm } from '../modal' import { error, info, success } from '../notification' @@ -1171,6 +1172,14 @@ export const createVgpu = (vm, { gpuGroup, vgpuType }) => export const deleteVgpu = vgpu => _call('vm.deleteVgpu', resolveIds({ vgpu })) +export const shareVm = (vm, resourceSet) => + confirm({ + title: _('shareVmInResourceSetModalTitle'), + body: _('shareVmInResourceSetModalMessage', { + self: renderXoItem(resourceSet), + }), + }).then(() => editVm(vm, { share: true }), noop) + // DISK --------------------------------------------------------------- export const createDisk = (name, size, sr, { vm, bootable, mode, position }) => diff --git a/packages/xo-web/src/icons.scss b/packages/xo-web/src/icons.scss index 87fbaa55b..53b2b29c3 100644 --- a/packages/xo-web/src/icons.scss +++ b/packages/xo-web/src/icons.scss @@ -356,6 +356,10 @@ @extend .text-primary; @extend .fa-ship; } + &-share { + @extend .fa; + @extend .fa-list-alt; + } } // 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 c8f9ef4b0..ea7f97474 100644 --- a/packages/xo-web/src/xo-app/vm/tab-advanced.js +++ b/packages/xo-web/src/xo-app/vm/tab-advanced.js @@ -24,9 +24,9 @@ import { osFamily, } from 'utils' import { - createVgpu, cloneVm, convertVmToTemplate, + createVgpu, deleteVgpu, deleteVm, editVm, @@ -34,6 +34,7 @@ import { recoveryStartVm, restartVm, resumeVm, + shareVm, stopVm, subscribeResourceSets, suspendVm, @@ -41,7 +42,12 @@ import { XEN_DEFAULT_CPU_WEIGHT, XEN_VIDEORAM_VALUES, } from 'xo' -import { createGetObjectsOfType, createSelector, isAdmin } from 'selectors' +import { + createGetObjectsOfType, + createSelector, + getCheckPermissions, + isAdmin, +} from 'selectors' const forceReboot = vm => restartVm(vm, true) const forceShutdown = vm => stopVm(vm, true) @@ -122,6 +128,31 @@ class ResourceSetItem extends Component { } } +@addSubscriptions({ + resourceSets: subscribeResourceSets, +}) +class ShareVmButton extends Component { + _shareVm = () => { + const { resourceSets, vm } = this.props + + return shareVm(vm, { + ...find(resourceSets, { id: vm.resourceSet }), + type: 'resourceSet', + }) + } + + render () { + return ( + + ) + } +} + class NewVgpu extends Component { get value () { return this.state @@ -284,340 +315,352 @@ export default connectStore(() => { createSelector(getVgpus, vgpus => map(vgpus, 'gpuGroup')) ) + const getCanAdministrate = createSelector( + getCheckPermissions, + (_, props) => props.vm.id, + (check, id) => check(id, 'administrate') + ) + return { + canAdministrate: getCanAdministrate, gpuGroup: getGpuGroup, isAdmin, vgpus: getVgpus, vgpuTypes: getVgpuTypes, } -})(({ container, gpuGroup, isAdmin, vgpus, vgpuTypes, vm }) => ( - - - - {vm.power_state === 'Running' && ( - - - - - - )} - {vm.power_state === 'Halted' && ( - - - - - - )} - {vm.power_state === 'Suspended' && ( - - - - - )} - - - - - -

{_('xenSettingsLabel')}

- - - - - {vm.uuid} - - - - - - {vm.virtualizationMode === 'pv' && ( +})( + ({ canAdministrate, container, gpuGroup, isAdmin, vgpus, vgpuTypes, vm }) => ( + + + + {(isAdmin || canAdministrate) && + vm.resourceSet != null && } + {vm.power_state === 'Running' && ( + + + + + + )} + {vm.power_state === 'Halted' && ( + + + + + + )} + {vm.power_state === 'Suspended' && ( + + + + + )} + + + + + +

{_('xenSettingsLabel')}

+
{_('uuid')}
{_('virtualizationMode')} - {vm.virtualizationMode === 'pv' - ? _('paraVirtualizedMode') - : _('hardwareVirtualizedMode')} -
+ - + + {vm.uuid} + + + - )} - - - - - - - - - - - - - - - - - - - - - {vm.virtualizationMode === 'hvm' && ( + {vm.virtualizationMode === 'pv' && ( + + + + + )} - + - )} - {vm.virtualizationMode === 'hvm' && ( - + + + + + - )} - {vm.vga === 'std' && ( - + - )} - -
{_('pvArgsLabel')}{_('uuid')}
{_('virtualizationMode')} - editVm(vm, { PV_args: value })} - /> + {vm.virtualizationMode === 'pv' + ? _('paraVirtualizedMode') + : _('hardwareVirtualizedMode')}
{_('cpuWeightLabel')} - editVm(vm, { cpuWeight: value })} - nullable - > - {vm.cpuWeight == null - ? _('defaultCpuWeight', { value: XEN_DEFAULT_CPU_WEIGHT }) - : vm.cpuWeight} - -
{_('cpuCapLabel')} - editVm(vm, { cpuCap: value })} - nullable - > - {vm.cpuCap == null - ? _('defaultCpuCap', { value: XEN_DEFAULT_CPU_CAP }) - : vm.cpuCap} - -
{_('autoPowerOn')} - editVm(vm, { auto_poweron: value })} - /> -
{_('ha')} - editVm(vm, { high_availability: value })} - /> -
{_('vmAffinityHost')} - -
{_('pvArgsLabel')} + editVm(vm, { PV_args: value })} + /> +
{_('vmVgpus')}{_('cpuWeightLabel')} - + editVm(vm, { cpuWeight: value })} + nullable + > + {vm.cpuWeight == null + ? _('defaultCpuWeight', { value: XEN_DEFAULT_CPU_WEIGHT }) + : vm.cpuWeight} +
{_('vmVga')}{_('cpuCapLabel')} + editVm(vm, { cpuCap: value })} + nullable + > + {vm.cpuCap == null + ? _('defaultCpuCap', { value: XEN_DEFAULT_CPU_CAP }) + : vm.cpuCap} + +
{_('autoPowerOn')} - editVm(vm, { vga: value ? 'std' : 'cirrus' }) - } + value={Boolean(vm.auto_poweron)} + onChange={value => editVm(vm, { auto_poweron: value })} />
{_('vmVideoram')}{_('ha')} - + editVm(vm, { high_availability: value })} + />
-
-

{_('vmLimitsLabel')}

- - - - - + + + + {vm.virtualizationMode === 'hvm' && ( + + + + + )} + {vm.virtualizationMode === 'hvm' && ( + + + + + )} + {vm.vga === 'std' && ( + + + + + )} + +
{_('vmCpuLimitsLabel')} - editVm(vm, { cpus })} - /> - / - {vm.power_state === 'Running' ? ( - vm.CPUs.max - ) : ( +
{_('vmAffinityHost')} + +
{_('vmVgpus')} + +
{_('vmVga')} + + editVm(vm, { vga: value ? 'std' : 'cirrus' }) + } + /> +
{_('vmVideoram')} + +
+
+

{_('vmLimitsLabel')}

+ + + + + - - - - - - - - - - -
{_('vmCpuLimitsLabel')} editVm(vm, { cpusStaticMax })} + value={vm.CPUs.number} + onChange={cpus => editVm(vm, { cpus })} /> - )} -
{_('vmCpuTopology')} - -
{_('vmMemoryLimitsLabel')} -

- Static: {formatSize(vm.memory.static[0])}/ - editVm(vm, { memoryStaticMax }) - } - /> -

-

- Dynamic:{' '} - editVm(vm, { memoryMin })} - />/ editVm(vm, { memoryMax })} - /> -

-
-
-

{_('guestOsLabel')}

- - - - - - - - - - - - - - - -
{_('xenToolsStatus')} - {_('xenToolsStatusValue', { - status: normalizeXenToolsStatus(vm.xenTools), - })} -
{_('osName')} - {isEmpty(vm.os_version) ? ( - _('unknownOsName') - ) : ( - -  {vm.os_version.name} - - )} -
{_('osKernel')} - {(vm.os_version && vm.os_version.uname) || _('unknownOsKernel')} -
-
-

{_('miscLabel')}

- - - - - - - - - - - -
{_('originalTemplate')} - {vm.other.base_template_name - ? vm.other.base_template_name - : _('unknownOriginalTemplate')} -
{_('resourceSet')} - {isAdmin ? ( - - editVm(vm, { - resourceSet: - resourceSet != null ? resourceSet.id : resourceSet, - }) - } - value={vm.resourceSet} - /> - ) : vm.resourceSet !== undefined ? ( - - ) : ( - _('resourceSetNone') - )} -
- -
-
-)) + / + {vm.power_state === 'Running' ? ( + vm.CPUs.max + ) : ( + editVm(vm, { cpusStaticMax })} + /> + )} + + + + {_('vmCpuTopology')} + + + + + + {_('vmMemoryLimitsLabel')} + +

+ Static: {formatSize(vm.memory.static[0])}/ + editVm(vm, { memoryStaticMax }) + } + /> +

+

+ Dynamic:{' '} + editVm(vm, { memoryMin })} + />/ editVm(vm, { memoryMax })} + /> +

+ + + + +
+

{_('guestOsLabel')}

+ + + + + + + + + + + + + + + +
{_('xenToolsStatus')} + {_('xenToolsStatusValue', { + status: normalizeXenToolsStatus(vm.xenTools), + })} +
{_('osName')} + {isEmpty(vm.os_version) ? ( + _('unknownOsName') + ) : ( + +  {vm.os_version.name} + + )} +
{_('osKernel')} + {(vm.os_version && vm.os_version.uname) || + _('unknownOsKernel')} +
+
+

{_('miscLabel')}

+ + + + + + + + + + + +
{_('originalTemplate')} + {vm.other.base_template_name + ? vm.other.base_template_name + : _('unknownOriginalTemplate')} +
{_('resourceSet')} + {isAdmin ? ( + + editVm(vm, { + resourceSet: + resourceSet != null ? resourceSet.id : resourceSet, + }) + } + value={vm.resourceSet} + /> + ) : vm.resourceSet !== undefined ? ( + + ) : ( + _('resourceSetNone') + )} +
+ + + + ) +)