diff --git a/src/api/vm.js b/src/api/vm.js index a790eaf40..b09a94446 100644 --- a/src/api/vm.js +++ b/src/api/vm.js @@ -1,5 +1,4 @@ import concat from 'lodash/concat' -import merge from 'lodash/merge' import { format } from 'json-rpc-peer' import { ignoreErrors } from 'promise-toolbox' import { @@ -360,15 +359,7 @@ async function delete_ ({ // Update resource sets const resourceSet = xapi.xo.getData(vm._xapiId, 'resourceSet') if (resourceSet != null) { - const resourceSetUsage = this.computeVmResourcesUsage(vm) - const ipPoolsUsage = await this.computeVmIpPoolsUsage(vm) - - ignoreErrors.call( - this.releaseLimitsInResourceSet( - merge(resourceSetUsage, ipPoolsUsage), - resourceSet - ) - ) + this.setVmResourceSet(vm._xapiId, null)::ignoreErrors() } return xapi.deleteVm(vm._xapiId, deleteDisks, force) @@ -504,11 +495,22 @@ migrate.resolve = { // ------------------------------------------------------------------- -export function set (params) { +export async function set (params) { const VM = extract(params, 'VM') const xapi = this.getXapi(VM) + const vmId = VM._xapiId - return xapi.editVm(VM._xapiId, params, async (limits, vm) => { + const resourceSetId = extract(params, 'resourceSet') + + if (resourceSetId !== undefined) { + if (this.user.permission !== 'admin') { + throw unauthorized() + } + + await this.setVmResourceSet(vmId, resourceSetId) + } + + return xapi.editVm(vmId, params, async (limits, vm) => { const resourceSet = xapi.xo.getData(vm, 'resourceSet') if (resourceSet) { @@ -576,6 +578,9 @@ set.params = { videoram: { type: ['string', 'number'], optional: true }, coresPerSocket: { type: ['string', 'number', 'null'], optional: true }, + + // Move the vm In to/Out of Self Service + resourceSet: { type: ['string', 'null'], optional: true }, } set.resolve = { diff --git a/src/xo-mixins/acls.js b/src/xo-mixins/acls.js index 3380f98ae..502261380 100644 --- a/src/xo-mixins/acls.js +++ b/src/xo-mixins/acls.js @@ -1,4 +1,5 @@ import checkAuthorization from 'xo-acl-resolver' +import { forEach, includes, map } from 'lodash' import { ModelAlreadyExists, @@ -8,9 +9,6 @@ import { } from '../models/acl' import { createRawObject, - forEach, - includes, - mapToArray, } from '../utils' // =================================================================== @@ -59,7 +57,7 @@ export default class { push.apply(acls, entries) })(acls.push) - await Promise.all(mapToArray( + await Promise.all(map( subjects, subject => this.getAclsForSubject(subject).then(pushAcls) )) @@ -133,6 +131,11 @@ export default class { ) } + async removeAclsForObject (objectId) { + const acls = this._acls + await acls.remove(map(await acls.get({ object: objectId }), 'id')) + } + // ----------------------------------------------------------------- async _getPermissionsByRole () { diff --git a/src/xo-mixins/resource-sets.js b/src/xo-mixins/resource-sets.js index 65a48ec33..75e5af8d2 100644 --- a/src/xo-mixins/resource-sets.js +++ b/src/xo-mixins/resource-sets.js @@ -1,20 +1,24 @@ -import every from 'lodash/every' -import keyBy from 'lodash/keyBy' -import remove from 'lodash/remove' -import some from 'lodash/some' import synchronized from 'decorator-synchronized' +import { + assign, + every, + forEach, + isObject, + keyBy, + map as mapToArray, + remove, + some, +} from 'lodash' import { noSuchObject, unauthorized, } from 'xo-common/api-errors' import { - forEach, + asyncMap, generateUnsecureToken, - isObject, lightSet, map, - mapToArray, streamToArray, } from '../utils' @@ -124,9 +128,12 @@ export default class { } } - computeVmResourcesUsage (vm) { - return computeVmResourcesUsage( - this._xo.getXapi(vm).getObject(vm._xapiId) + async computeVmResourcesUsage (vm) { + return assign( + computeVmResourcesUsage( + this._xo.getXapi(vm).getObject(vm._xapiId) + ), + await this._xo.computeVmIpPoolsUsage(vm) ) } @@ -344,4 +351,34 @@ export default class { await Promise.all(mapToArray(sets, set => this._save(set))) } + + async setVmResourceSet (vmId, resourceSetId) { + const xapi = this._xo.getXapi(vmId) + const previousResourceSetId = xapi.xo.getData(vmId, 'resourceSet') + + if (resourceSetId === previousResourceSetId || (previousResourceSetId === undefined && resourceSetId === null)) { + return + } + + const resourcesUsage = await this.computeVmResourcesUsage(this._xo.getObject(vmId)) + + if (resourceSetId != null) { + await this.allocateLimitsInResourceSet(resourcesUsage, resourceSetId) + } + if (previousResourceSetId !== undefined) { + await this.releaseLimitsInResourceSet(resourcesUsage, previousResourceSetId) + } + + await xapi.xo.setData(vmId, 'resourceSet', resourceSetId === undefined ? null : resourceSetId) + + if (previousResourceSetId !== undefined) { + await this._xo.removeAclsForObject(vmId) + } + if (resourceSetId != null) { + const { subjects } = await this.getResourceSet(resourceSetId) + await asyncMap(subjects, subject => + this._xo.addAcl(subject, vmId, 'admin') + ) + } + } } diff --git a/src/xo-mixins/xen-servers.js b/src/xo-mixins/xen-servers.js index b787999d5..9bd94d0ab 100644 --- a/src/xo-mixins/xen-servers.js +++ b/src/xo-mixins/xen-servers.js @@ -311,7 +311,7 @@ export default class { await xapi._updateObjectMapProperty( xapi.getObject(id), 'other_config', - { [`xo:${camelToSnakeCase(key)}`]: JSON.stringify(value) } + { [`xo:${camelToSnakeCase(key)}`]: value !== null ? JSON.stringify(value) : value } ) // Register the updated object.