feat: share a vm in a resource set (#2611)

Fixes #2589
This commit is contained in:
badrAZ 2018-02-13 15:24:20 +01:00 committed by Julien Fontanet
parent 8b05486945
commit 8971d218bb
6 changed files with 392 additions and 318 deletions

View File

@ -500,7 +500,6 @@ export async function set (params) {
const vmId = VM._xapiId const vmId = VM._xapiId
const resourceSetId = extract(params, 'resourceSet') const resourceSetId = extract(params, 'resourceSet')
if (resourceSetId !== undefined) { if (resourceSetId !== undefined) {
if (this.user.permission !== 'admin') { if (this.user.permission !== 'admin') {
throw unauthorized() throw unauthorized()
@ -509,6 +508,11 @@ export async function set (params) {
await this.setVmResourceSet(vmId, resourceSetId) await this.setVmResourceSet(vmId, resourceSetId)
} }
const share = extract(params, 'share')
if (share) {
await this.shareVmResourceSet(vmId)
}
return xapi.editVm(vmId, params, async (limits, vm) => { return xapi.editVm(vmId, params, async (limits, vm) => {
const resourceSet = xapi.xo.getData(vm, 'resourceSet') const resourceSet = xapi.xo.getData(vm, 'resourceSet')
@ -580,6 +584,8 @@ set.params = {
// Move the vm In to/Out of Self Service // Move the vm In to/Out of Self Service
resourceSet: { type: ['string', 'null'], optional: true }, resourceSet: { type: ['string', 'null'], optional: true },
share: { type: 'boolean', optional: true },
} }
set.resolve = { set.resolve = {

View File

@ -391,10 +391,18 @@ export default class {
await this._xo.removeAclsForObject(vmId) await this._xo.removeAclsForObject(vmId)
} }
if (resourceSetId != null) { if (resourceSetId != null) {
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) const { subjects } = await this.getResourceSet(resourceSetId)
await asyncMap(subjects, subject => await asyncMap(subjects, subject => this._xo.addAcl(subject, vmId, 'admin'))
this._xo.addAcl(subject, vmId, 'admin')
)
}
} }
} }

View File

@ -844,6 +844,7 @@ const messages = {
// ----- VM advanced tab ----- // ----- VM advanced tab -----
vmRemoveButton: 'Remove', vmRemoveButton: 'Remove',
vmConvertButton: 'Convert', vmConvertButton: 'Convert',
vmShareButton: 'Share',
xenSettingsLabel: 'Xen settings', xenSettingsLabel: 'Xen settings',
guestOsLabel: 'Guest OS', guestOsLabel: 'Guest OS',
miscLabel: 'Misc', miscLabel: 'Misc',
@ -1252,6 +1253,9 @@ const messages = {
deleteRemotesModalMessage: deleteRemotesModalMessage:
'Are you sure you want to delete {nRemotes, number} remote{nRemotes, plural, one {} other {s}}?', 'Are you sure you want to delete {nRemotes, number} remote{nRemotes, plural, one {} other {s}}?',
revertVmModalTitle: 'Revert your VM', 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}}', deleteVifsModalTitle: 'Delete VIF{nVifs, plural, one {} other {s}}',
deleteVifsModalMessage: deleteVifsModalMessage:
'Are you sure you want to delete {nVifs, number} VIF{nVifs, plural, one {} other {s}}?', 'Are you sure you want to delete {nVifs, number} VIF{nVifs, plural, one {} other {s}}?',

View File

@ -24,6 +24,7 @@ import { forbiddenOperation, noHostsAvailable } from 'xo-common/api-errors'
import _ from '../intl' import _ from '../intl'
import invoke from '../invoke' import invoke from '../invoke'
import logError from '../log-error' import logError from '../log-error'
import renderXoItem from '../render-xo-item'
import store from 'store' import store from 'store'
import { alert, chooseAction, confirm } from '../modal' import { alert, chooseAction, confirm } from '../modal'
import { error, info, success } from '../notification' 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 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 --------------------------------------------------------------- // DISK ---------------------------------------------------------------
export const createDisk = (name, size, sr, { vm, bootable, mode, position }) => export const createDisk = (name, size, sr, { vm, bootable, mode, position }) =>

View File

@ -356,6 +356,10 @@
@extend .text-primary; @extend .text-primary;
@extend .fa-ship; @extend .fa-ship;
} }
&-share {
@extend .fa;
@extend .fa-list-alt;
}
} }
// Generic states // Generic states

View File

@ -24,9 +24,9 @@ import {
osFamily, osFamily,
} from 'utils' } from 'utils'
import { import {
createVgpu,
cloneVm, cloneVm,
convertVmToTemplate, convertVmToTemplate,
createVgpu,
deleteVgpu, deleteVgpu,
deleteVm, deleteVm,
editVm, editVm,
@ -34,6 +34,7 @@ import {
recoveryStartVm, recoveryStartVm,
restartVm, restartVm,
resumeVm, resumeVm,
shareVm,
stopVm, stopVm,
subscribeResourceSets, subscribeResourceSets,
suspendVm, suspendVm,
@ -41,7 +42,12 @@ import {
XEN_DEFAULT_CPU_WEIGHT, XEN_DEFAULT_CPU_WEIGHT,
XEN_VIDEORAM_VALUES, XEN_VIDEORAM_VALUES,
} from 'xo' } from 'xo'
import { createGetObjectsOfType, createSelector, isAdmin } from 'selectors' import {
createGetObjectsOfType,
createSelector,
getCheckPermissions,
isAdmin,
} from 'selectors'
const forceReboot = vm => restartVm(vm, true) const forceReboot = vm => restartVm(vm, true)
const forceShutdown = vm => stopVm(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 (
<TabButton
btnStyle='primary'
handler={this._shareVm}
icon='vm-share'
labelId='vmShareButton'
/>
)
}
}
class NewVgpu extends Component { class NewVgpu extends Component {
get value () { get value () {
return this.state return this.state
@ -284,16 +315,26 @@ export default connectStore(() => {
createSelector(getVgpus, vgpus => map(vgpus, 'gpuGroup')) createSelector(getVgpus, vgpus => map(vgpus, 'gpuGroup'))
) )
const getCanAdministrate = createSelector(
getCheckPermissions,
(_, props) => props.vm.id,
(check, id) => check(id, 'administrate')
)
return { return {
canAdministrate: getCanAdministrate,
gpuGroup: getGpuGroup, gpuGroup: getGpuGroup,
isAdmin, isAdmin,
vgpus: getVgpus, vgpus: getVgpus,
vgpuTypes: getVgpuTypes, vgpuTypes: getVgpuTypes,
} }
})(({ container, gpuGroup, isAdmin, vgpus, vgpuTypes, vm }) => ( })(
({ canAdministrate, container, gpuGroup, isAdmin, vgpus, vgpuTypes, vm }) => (
<Container> <Container>
<Row> <Row>
<Col className='text-xs-right'> <Col className='text-xs-right'>
{(isAdmin || canAdministrate) &&
vm.resourceSet != null && <ShareVmButton vm={vm} />}
{vm.power_state === 'Running' && ( {vm.power_state === 'Running' && (
<span> <span>
<TabButton <TabButton
@ -578,7 +619,8 @@ export default connectStore(() => {
<tr> <tr>
<th>{_('osKernel')}</th> <th>{_('osKernel')}</th>
<td> <td>
{(vm.os_version && vm.os_version.uname) || _('unknownOsKernel')} {(vm.os_version && vm.os_version.uname) ||
_('unknownOsKernel')}
</td> </td>
</tr> </tr>
</tbody> </tbody>
@ -620,4 +662,5 @@ export default connectStore(() => {
</Col> </Col>
</Row> </Row>
</Container> </Container>
)) )
)