Implement method.resolve
API methods can now have some of their objects automatically fetched: ``` method.resolve = { vm: ['vm', 'VM'], }; ``` The key is the name of the property with which the object will be attached in the parameters dictionary, the first entry of the array is the name of the parameter to use to fetch the object and the second the expected type/types of the object. Note that permissions are automatically checked via the ACLs.
This commit is contained in:
parent
ed6fcf5ae7
commit
1a71cc9223
76
src/api.js
76
src/api.js
@ -21,25 +21,30 @@ import {
|
||||
|
||||
// FIXME: this function is specific to XO and should not be defined in
|
||||
// this file.
|
||||
let checkPermission = coroutine(function *(permission) {
|
||||
function checkPermission(method) {
|
||||
/* jshint validthis: true */
|
||||
|
||||
let userId = this.session.get('user_id', undefined);
|
||||
let {permission} = method;
|
||||
|
||||
if (userId === undefined) {
|
||||
// No requirement.
|
||||
if (permission === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
let {user} = this;
|
||||
if (!user) {
|
||||
throw new Unauthorized();
|
||||
}
|
||||
|
||||
// The only requirement is login.
|
||||
if (!permission) {
|
||||
return;
|
||||
}
|
||||
|
||||
let user = yield this.users.first(userId);
|
||||
|
||||
if (!user.hasPermission(permission)) {
|
||||
throw new Unauthorized();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
@ -59,6 +64,52 @@ function checkParams(method, params) {
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------
|
||||
|
||||
function resolveParams(method, params) {
|
||||
var resolve = method.resolve;
|
||||
if (!resolve) {
|
||||
return params;
|
||||
}
|
||||
|
||||
let {user} = this;
|
||||
if (!user) {
|
||||
throw new Unauthorized();
|
||||
}
|
||||
|
||||
let userId = user.get('id');
|
||||
let isAdmin = this.user.hasPermission('admin');
|
||||
|
||||
let promises = [];
|
||||
try {
|
||||
forEach(resolve, ([param, types], key) => {
|
||||
let object = this.getObject(params[param], types);
|
||||
|
||||
// This parameter has been handled, remove it.
|
||||
delete params[param];
|
||||
|
||||
|
||||
// Register this new value.
|
||||
params[key] = object;
|
||||
|
||||
if (!isAdmin) {
|
||||
promises.push(this.acls.exists({
|
||||
subject: userId,
|
||||
object: object.id,
|
||||
}).then(function (exists) {
|
||||
if (!exists) {
|
||||
throw new Unauthorized();
|
||||
}
|
||||
}));
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
throw new NoSuchObject();
|
||||
}
|
||||
|
||||
return Bluebird.all(promises).return(params);
|
||||
}
|
||||
|
||||
//====================================================================
|
||||
|
||||
function getMethodsInfo() {
|
||||
@ -167,12 +218,19 @@ export default class Api {
|
||||
context.api = this; // Used by system.*().
|
||||
context.session = session;
|
||||
|
||||
if ('permission' in method) {
|
||||
return checkPermission.call(context, method.permission);
|
||||
}
|
||||
// FIXME: too coupled with XO.
|
||||
// Fetch and inject the current user.
|
||||
let userId = session.get('user_id', undefined);
|
||||
return userId === undefined ? null : context.users.first(userId);
|
||||
}).then(function (user) {
|
||||
context.user = user;
|
||||
|
||||
return checkPermission.call(context, method);
|
||||
}).then(() => {
|
||||
checkParams(method, params);
|
||||
|
||||
return resolveParams.call(context, method, params);
|
||||
}).then(params => {
|
||||
return method.call(context, params);
|
||||
}).then(
|
||||
result => {
|
||||
|
@ -224,19 +224,14 @@ exports.create.params = {
|
||||
}
|
||||
}
|
||||
|
||||
exports.delete = $coroutine ({id, delete_disks: deleteDisks}) ->
|
||||
try
|
||||
VM = @getObject id, ['VM', 'VM-snapshot']
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
if $isVMRunning VM
|
||||
exports.delete = $coroutine ({vm, delete_disks: deleteDisks}) ->
|
||||
if $isVMRunning vm
|
||||
@throw 'INVALID_PARAMS', 'The VM can only be deleted when halted'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
xapi = @getXAPI vm
|
||||
|
||||
if deleteDisks
|
||||
$forEach VM.$VBDs, (ref) =>
|
||||
$forEach vm.$VBDs, (ref) =>
|
||||
try
|
||||
VBD = @getObject ref, 'VBD'
|
||||
catch e
|
||||
@ -248,10 +243,9 @@ exports.delete = $coroutine ({id, delete_disks: deleteDisks}) ->
|
||||
|
||||
return
|
||||
|
||||
$wait xapi.call 'VM.destroy', VM.ref
|
||||
$wait xapi.call 'VM.destroy', vm.ref
|
||||
|
||||
return true
|
||||
exports.delete.permission = 'admin'
|
||||
exports.delete.params = {
|
||||
id: { type: 'string' }
|
||||
|
||||
@ -260,18 +254,16 @@ exports.delete.params = {
|
||||
type: 'boolean'
|
||||
}
|
||||
}
|
||||
exports.delete.resolve = {
|
||||
vm: ['id', ['VM', 'VM-snapshot']]
|
||||
}
|
||||
|
||||
exports.ejectCd = $coroutine ({id}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
exports.ejectCd = $coroutine ({vm}) ->
|
||||
xapi = @getXAPI vm
|
||||
|
||||
# Finds the CD drive.
|
||||
cdDriveRef = null
|
||||
$forEach (@getObjects VM.$VBDs), (VBD) ->
|
||||
$forEach (@getObjects vm.$VBDs), (VBD) ->
|
||||
if VBD.is_cd_drive
|
||||
cdDriveRef = VBD.ref
|
||||
return false
|
||||
@ -282,23 +274,19 @@ exports.ejectCd = $coroutine ({id}) ->
|
||||
$wait xapi.call 'VBD.destroy', cdDriveRef
|
||||
|
||||
return true
|
||||
exports.ejectCd.permission = 'admin'
|
||||
exports.ejectCd.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
exports.ejectCd.resolve = {
|
||||
vm: ['id', 'VM']
|
||||
}
|
||||
|
||||
exports.insertCd = $coroutine ({id, cd_id, force}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
VDI = @getObject cd_id, 'VDI'
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
exports.insertCd = $coroutine ({vm, vdi, force}) ->
|
||||
xapi = @getXAPI vm
|
||||
|
||||
# Finds the CD drive.
|
||||
cdDrive = null
|
||||
$forEach (@getObjects VM.$VBDs), (VBD) ->
|
||||
$forEach (@getObjects vm.$VBDs), (VBD) ->
|
||||
if VBD.is_cd_drive
|
||||
cdDrive = VBD
|
||||
return false
|
||||
@ -321,37 +309,33 @@ exports.insertCd = $coroutine ({id, cd_id, force}) ->
|
||||
qos_algorithm_type: ''
|
||||
type: 'CD'
|
||||
unpluggable: true
|
||||
userdevice: ($wait xapi.call 'VM.get_allowed_VBD_devices', VM.ref)[0]
|
||||
userdevice: ($wait xapi.call 'VM.get_allowed_VBD_devices', vm.ref)[0]
|
||||
VDI: 'OpaqueRef:NULL'
|
||||
VM: VM.ref
|
||||
VM: vm.ref
|
||||
}
|
||||
|
||||
$wait xapi.call 'VBD.insert', cdDriveRef, VDI.ref
|
||||
$wait xapi.call 'VBD.insert', cdDriveRef, vdi.ref
|
||||
|
||||
return true
|
||||
exports.insertCd.permission = 'admin'
|
||||
exports.insertCd.params = {
|
||||
id: { type: 'string' }
|
||||
cd_id: { type: 'string' }
|
||||
force: { type: 'boolean' }
|
||||
}
|
||||
exports.insertCd.resolve = {
|
||||
vm: ['id', 'VM'],
|
||||
vdi: ['cd_id', 'VDI'],
|
||||
}
|
||||
|
||||
exports.migrate = $coroutine ({id, host_id}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
host = @getObject host_id, 'host'
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
unless $isVMRunning VM
|
||||
exports.migrate = $coroutine ({vm, host}) ->
|
||||
unless $isVMRunning vm
|
||||
@throw 'INVALID_PARAMS', 'The VM can only be migrated when running'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
xapi = @getXAPI vm
|
||||
|
||||
$wait xapi.call 'VM.pool_migrate', VM.ref, host.ref, {'force': 'true'}
|
||||
$wait xapi.call 'VM.pool_migrate', vm.ref, host.ref, {'force': 'true'}
|
||||
|
||||
return true
|
||||
exports.migrate.permission = 'admin'
|
||||
exports.migrate.params = {
|
||||
# Identifier of the VM to migrate.
|
||||
id: { type: 'string' }
|
||||
@ -359,6 +343,10 @@ exports.migrate.params = {
|
||||
# Identifier of the host to migrate to.
|
||||
host_id: { type: 'string' }
|
||||
}
|
||||
exports.migrate.resolve = {
|
||||
vm: ['id', 'VM']
|
||||
host: ['host_id', 'host']
|
||||
}
|
||||
|
||||
exports.migrate_pool = $coroutine ({
|
||||
id
|
||||
@ -545,120 +533,98 @@ exports.set.params = {
|
||||
memory: { type: 'integer', optional: true }
|
||||
}
|
||||
|
||||
exports.restart = $coroutine ({id, force}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
exports.restart = $coroutine ({vm, force}) ->
|
||||
xapi = @getXAPI(vm)
|
||||
|
||||
try
|
||||
# Attempts a clean reboot.
|
||||
$wait xapi.call 'VM.clean_reboot', VM.ref
|
||||
$wait xapi.call 'VM.clean_reboot', vm.ref
|
||||
catch error
|
||||
return unless error[0] is 'VM_MISSING_PV_DRIVERS'
|
||||
|
||||
@throw 'INVALID_PARAMS' unless force
|
||||
|
||||
$wait xapi.call 'VM.hard_reboot', VM.ref
|
||||
$wait xapi.call 'VM.hard_reboot', vm.ref
|
||||
|
||||
return true
|
||||
exports.restart.permission = 'admin'
|
||||
exports.restart.params = {
|
||||
id: { type: 'string' }
|
||||
force: { type: 'boolean' }
|
||||
}
|
||||
exports.restart.resolve = {
|
||||
vm: ['id', 'VM']
|
||||
}
|
||||
|
||||
exports.clone = $coroutine ({id, name, full_copy}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
exports.clone = $coroutine ({vm, name, full_copy}) ->
|
||||
xapi = @getXAPI vm
|
||||
if full_copy
|
||||
$wait xapi.call 'VM.copy', VM.ref, name, ''
|
||||
$wait xapi.call 'VM.copy', vm.ref, name, ''
|
||||
else
|
||||
$wait xapi.call 'VM.clone', VM.ref, name
|
||||
$wait xapi.call 'VM.clone', vm.ref, name
|
||||
|
||||
return true
|
||||
exports.clone.permission = 'admin'
|
||||
exports.clone.params = {
|
||||
id: { type: 'string' }
|
||||
name: { type: 'string' }
|
||||
full_copy: { type: 'boolean' }
|
||||
}
|
||||
exports.clone.resolve = {
|
||||
vm: ['id', 'VM']
|
||||
}
|
||||
|
||||
# TODO: rename convertToTemplate()
|
||||
exports.convert = $coroutine ({id}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
$wait xapi.call 'VM.set_is_a_template', VM.ref, true
|
||||
exports.convert = $coroutine ({vm}) ->
|
||||
$wait @getXAPI(vm).call 'VM.set_is_a_template', vm.ref, true
|
||||
|
||||
return true
|
||||
exports.convert.permission = 'admin'
|
||||
exports.convert.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
exports.convert.resolve = {
|
||||
vm: ['id', 'VM']
|
||||
}
|
||||
|
||||
exports.snapshot = $coroutine ({id, name}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
ref = $wait (@getXAPI VM).call 'VM.snapshot', VM.ref, name
|
||||
|
||||
return ref
|
||||
exports.snapshot.permission = 'admin'
|
||||
exports.snapshot = $coroutine ({vm, name}) ->
|
||||
return $wait @getXAPI(vm).call 'VM.snapshot', vm.ref, name
|
||||
exports.snapshot.params = {
|
||||
id: { type: 'string' }
|
||||
name: { type: 'string' }
|
||||
}
|
||||
exports.snapshot.resolve = {
|
||||
vm: ['id', 'VM']
|
||||
}
|
||||
|
||||
exports.start = $coroutine ({id}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
$wait (@getXAPI VM).call(
|
||||
'VM.start', VM.ref
|
||||
exports.start = $coroutine ({vm}) ->
|
||||
$wait @getXAPI(vm).call(
|
||||
'VM.start', vm.ref
|
||||
false # Start paused?
|
||||
false # Skips the pre-boot checks?
|
||||
)
|
||||
|
||||
return true
|
||||
exports.start.permission = 'admin'
|
||||
exports.start.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
exports.start.resolve = {
|
||||
vm: ['id', 'VM']
|
||||
}
|
||||
|
||||
|
||||
# TODO: implements timeout.
|
||||
# - if !force → clean shutdown
|
||||
# - if force is true → hard shutdown
|
||||
# - if force is integer → clean shutdown and after force seconds, hard shutdown.
|
||||
exports.stop = $coroutine ({id, force}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
exports.stop = $coroutine ({vm, force}) ->
|
||||
xapi = @getXAPI vm
|
||||
|
||||
# Hard shutdown
|
||||
if force
|
||||
$wait xapi.call 'VM.hard_shutdown', VM.ref
|
||||
$wait xapi.call 'VM.hard_shutdown', vm.ref
|
||||
return true
|
||||
|
||||
# Clean shutdown
|
||||
try
|
||||
$wait xapi.call 'VM.clean_shutdown', VM.ref
|
||||
$wait xapi.call 'VM.clean_shutdown', vm.ref
|
||||
catch error
|
||||
if error[0] is 'VM_MISSING_PV_DRIVERS'
|
||||
# TODO: Improve reporting: this message is unclear.
|
||||
@ -667,65 +633,54 @@ exports.stop = $coroutine ({id, force}) ->
|
||||
throw error
|
||||
|
||||
return true
|
||||
exports.stop.permission = 'admin'
|
||||
exports.stop.params = {
|
||||
id: { type: 'string' }
|
||||
force: { type: 'boolean', optional: true }
|
||||
}
|
||||
exports.stop.resolve = {
|
||||
vm: ['id', 'VM']
|
||||
}
|
||||
|
||||
exports.suspend = $coroutine ({id}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
|
||||
$wait xapi.call 'VM.suspend', VM.ref
|
||||
exports.suspend = $coroutine ({vm}) ->
|
||||
$wait @getXAPI(vm).call 'VM.suspend', vm.ref
|
||||
|
||||
return true
|
||||
exports.suspend.permission = 'admin'
|
||||
exports.suspend.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
exports.suspend.resolve = {
|
||||
vm: ['id', 'VM']
|
||||
}
|
||||
|
||||
exports.resume = $coroutine ({id, force}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM'
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
|
||||
exports.resume = $coroutine ({vm, force}) ->
|
||||
# FIXME: WTF this is?
|
||||
if not force
|
||||
force = true
|
||||
|
||||
$wait xapi.call 'VM.resume', VM.ref, false, force
|
||||
$wait @getXAPI(vm).call 'VM.resume', vm.ref, false, force
|
||||
|
||||
return true
|
||||
exports.resume.permission = 'admin'
|
||||
exports.resume.params = {
|
||||
id: { type: 'string' }
|
||||
force: { type: 'boolean', optional: true }
|
||||
}
|
||||
exports.resume.resolve = {
|
||||
vm: ['id', 'VM']
|
||||
}
|
||||
|
||||
# revert a snapshot to its parent VM
|
||||
exports.revert = $coroutine ({id}) ->
|
||||
try
|
||||
VM = @getObject id, 'VM-snapshot'
|
||||
catch
|
||||
@throw 'NO_SUCH_OBJECT'
|
||||
|
||||
xapi = @getXAPI VM
|
||||
|
||||
exports.revert = $coroutine ({snapshot}) ->
|
||||
# Attempts a revert from this snapshot to its parent VM
|
||||
$wait xapi.call 'VM.revert', VM.ref
|
||||
$wait @getXAPI(snapshot).call 'VM.revert', snapshot.ref
|
||||
|
||||
return true
|
||||
exports.revert.permission = 'admin'
|
||||
exports.revert.params = {
|
||||
id: { type: 'string' }
|
||||
}
|
||||
exports.revert.resolve = {
|
||||
snapshot: ['id', 'VM-snapshot']
|
||||
}
|
||||
|
||||
exports.export = $coroutine ({vm, compress}) ->
|
||||
compress ?= true
|
||||
|
Loading…
Reference in New Issue
Block a user