vm.create() refactoring.

This commit is contained in:
Julien Fontanet 2015-06-11 16:30:59 +02:00
parent 6d87a1a604
commit db6d48f8ca
4 changed files with 217 additions and 199 deletions

View File

@ -7,6 +7,7 @@ export {
InvalidJson,
InvalidParameters,
InvalidRequest,
JsonRpcError,
MethodNotFound
} from 'json-rpc-protocol'

View File

@ -12,6 +12,7 @@ import schemaInspector from 'schema-inspector'
import {
InvalidParameters,
JsonRpcError,
MethodNotFound,
NoSuchObject,
Unauthorized
@ -289,11 +290,12 @@ export default class Api {
context.user = await context._getUser(userId)
}
await checkPermission.call(context, method)
checkParams(method, params)
await resolveParams.call(context, method, params)
try {
await checkPermission.call(context, method)
checkParams(method, params)
await resolveParams.call(context, method, params)
let result = await method.call(context, params)
// If nothing was returned, consider this operation a success
@ -306,7 +308,11 @@ export default class Api {
return result
} catch (error) {
debug('Error: %s(...) → %s', name, error)
if (error instanceof JsonRpcError) {
debug('Error: %s(...) → %s', name, error)
} else {
console.error(error && error.stack || error)
}
throw error
}

View File

@ -28,7 +28,6 @@ $isVMRunning = do ->
#=====================================================================
# TODO: Implement ACLs
# FIXME: Make the method as atomic as possible.
create = $coroutine ({
installation
name_label
@ -36,135 +35,13 @@ create = $coroutine ({
VDIs
VIFs
}) ->
# Gets the corresponding connection.
xapi = @getXAPI template
vm = yield @getXAPI(template).createVm(template.id, name_label, {
installRepository: installation.repository,
vdis: VDIs,
vifs: VIFs
})
# Clones the VM from the template.
vm = yield xapi.cloneVm(template.ref, name_label)
# TODO: if there is an error from now, removes this VM.
# TODO: remove existing VIFs.
# Creates associated virtual interfaces.
#
# FIXME: device n may already exists, we have to find the first
# free device number.
deviceId = 0
yield Bluebird.all(map(VIFs, (VIF) =>
return xapi.createVirtualInterface(vm.$id, VIF.network, {
position: deviceId++
})
))
# TODO: ? yield xapi.call 'VM.set_PV_args', vm.$ref, 'noninteractive'
# Updates the number of existing vCPUs.
if CPUs?
yield xapi.call 'VM.set_VCPUs_at_startup', vm.$ref, CPUs
# TODO: remove existing VDIs (o make sure we have only those we
# asked.
#
# Problem: how to know which VMs to clones for instance.
if VDIs?
# Transform the VDIs specs to conform to XAPI.
$forEach VDIs, (VDI, key) ->
VDI.bootable = if VDI.bootable then 'true' else 'false'
VDI.size = "#{VDI.size}"
VDI.sr = VDI.SR
delete VDI.SR
# Preparation for the XML generation.
VDIs[key] = { $: VDI }
return
# Converts the provision disks spec to XML.
VDIs = $js2xml {
provision: {
disk: VDIs
}
}
# Replace the existing entry in the VM object.
try yield xapi.call 'VM.remove_from_other_config', vm.$ref, 'disks'
yield xapi.call 'VM.add_to_other_config', vm.$ref, 'disks', VDIs
try yield xapi.call(
'VM.remove_from_other_config'
vm.$ref
'install-repository'
)
if installation
switch installation.method
when 'cdrom'
yield xapi.call(
'VM.add_to_other_config', vm.$ref
'install-repository', 'cdrom'
)
when 'ftp', 'http', 'nfs'
yield xapi.call(
'VM.add_to_other_config', vm.$ref
'install-repository', installation.repository
)
else
@throw(
'INVALID_PARAMS'
"Unsupported installation method #{installation.method}"
)
# Creates the VDIs and executes the initial steps of the
# installation.
yield xapi.call 'VM.provision', vm.$ref
# Gets the VM record.
VM = yield xapi.call 'VM.get_record', vm.$ref
if installation.method is 'cdrom'
# Gets the VDI containing the ISO to mount.
try
VDIref = (@getObject installation.repository, 'VDI').ref
catch
@throw 'NO_SUCH_OBJECT', 'installation.repository'
# Finds the VBD associated to the newly created VM which is a
# CD.
CD_drive = null
for ref in VM.VBDs
VBD = yield xapi.call 'VBD.get_record', vm.$ref
# TODO: Checks it has been correctly retrieved.
if VBD.type is 'CD'
CD_drive = VBD.ref
break
# No CD drives have been found, creates one.
unless CD_drive
# See: https://github.com/xenserver/xenadmin/blob/da00b13bb94603b369b873b0a555d44f15fa0ca5/XenModel/Actions/VM/CreateVMAction.cs#L370
CD_drive = yield xapi.call 'VBD.create', {
bootable: true
device: ''
empty: true
mode: 'RO'
other_config: {}
qos_algorithm_params: {}
qos_algorithm_type: ''
type: 'CD'
unpluggable: true
userdevice: (yield xapi.call 'VM.get_allowed_VBD_devices', vm.$ref)[0]
VDI: 'OpaqueRef:NULL'
VM: vm.$ref
}
# If the CD drive as not been found, throws.
@throw 'NO_SUCH_OBJECT' unless CD_drive
# Mounts the VDI into the VBD.
yield xapi.call 'VBD.insert', CD_drive, VDIref
else
yield xapi.call 'VM.provision', vm.$ref
# The VM should be properly created.
return vm.uuid
return vm.id
create.permission = 'admin'
@ -280,41 +157,8 @@ exports.ejectCd = ejectCd
#---------------------------------------------------------------------
insertCd = $coroutine ({vm, vdi, force}) ->
xapi = @getXAPI vm
# Finds the CD drive.
cdDrive = null
$forEach (@getObjects vm.$VBDs), (VBD) ->
if VBD.is_cd_drive
cdDrive = VBD
return false
return
if cdDrive
cdDriveRef = cdDrive.ref
if cdDrive.VDI
@throw 'INVALID_PARAMS' unless force
yield xapi.call 'VBD.eject', cdDriveRef
else
cdDriveRef = yield xapi.call 'VBD.create', {
bootable: true
device: ''
empty: true
mode: 'RO'
other_config: {}
qos_algorithm_params: {}
qos_algorithm_type: ''
type: 'CD'
unpluggable: true
userdevice: (yield xapi.call 'VM.get_allowed_VBD_devices', vm.ref)[0]
VDI: 'OpaqueRef:NULL'
VM: vm.ref
}
yield xapi.call 'VBD.insert', cdDriveRef, vdi.ref
return true
yield @getXAPI(vm).insertCdIntoVm(vdi.id, vm.id, force)
return
insertCd.params = {
id: { type: 'string' }
@ -582,13 +426,14 @@ exports.restart = restart
#---------------------------------------------------------------------
clone = $coroutine ({vm, name, full_copy}) ->
xapi = @getXAPI vm
if full_copy
yield xapi.call 'VM.copy', vm.ref, name, ''
else
yield xapi.call 'VM.clone', vm.ref, name
xapi = @getXAPI(vm)
return true
newVm = yield if full_copy
xapi.copyVm(vm.ref, null, name)
else
xapi.cloneVm(vm.ref, name)
return newVm.$id
clone.params = {
id: { type: 'string' }
@ -818,7 +663,11 @@ exports.import = import_
# FIXME: if position is used, all other disks after this position
# should be shifted.
attachDisk = $coroutine ({vm, vdi, position, mode, bootable}) ->
yield @getXAPI(vm).attachVdiToVm(vdi.id, vm.id, {bootable, mode, position})
yield @getXAPI(vm).attachVdiToVm(vdi.id, vm.id, {
bootable,
position,
readOnly: mode is 'RO'
})
return
attachDisk.params = {

View File

@ -13,7 +13,12 @@ import {
} from 'xen-api'
import {debounce} from './decorators'
import {ensureArray, noop, parseXml, pFinally} from './utils'
import {
ensureArray,
formatXml,
noop, parseXml,
pFinally
} from './utils'
import {JsonRpcError} from './api-errors'
const debug = createDebug('xo:xapi')
@ -404,10 +409,9 @@ export default class Xapi extends XapiBase {
// =================================================================
async _deleteVdi (vdiId) {
const vdi = this.getObject(vdiId)
async _cloneVm (vm, nameLabel = vm.name_label) {
return await this.call('VM.clone', vm.$ref, nameLabel)
await this.call('VDI.destroy', vdi.$ref)
}
async _snapshotVm (vm, nameLabel = vm.name_label) {
@ -419,15 +423,98 @@ export default class Xapi extends XapiBase {
return ref
}
async cloneVm (vmId, name_label = undefined) {
async cloneVm (vmId, nameLabel = undefined) {
return this._getOrWaitObject(
await this._cloneVm(this.getObject(vmId), nameLabel)
)
}
async copyVm (vmId, srId = null, nameLabel = undefined) {
const vm = this.getObject(vmId)
if (name_label == null) {
({name_label} = vm)
const srRef = (srId == null) ?
'' :
this.getObject(srId).$ref
return await this._getOrWaitObject(
await this.call('VM.copy', vm.$ref, nameLabel || vm.nameLabel, srRef)
)
}
// TODO: clean up on error.
async createVm (templateId, nameLabel, {
cpus = undefined,
installRepository = undefined,
vdis = [],
vifs = []
} = {}) {
const template = this.getObject(templateId)
// Clones the template.
const vm = await this._getOrWaitObject(
await this._cloneVm(template, nameLabel)
)
// Creates the VIFs.
//
// TODO: removes existing VIFs.
{
let position = 0
await Promise.all(map(vifs, vif => this._createVif(
vm,
this.getObject(vif.network),
{ position: position++ }
)))
}
const ref = this.call('VM.clone', vm.$ref, name_label)
// TODO: ? await this.call('VM.set_PV_args', vm.$ref, 'noninteractive')
return await this._getOrWaitObject(ref)
// Sets the number of CPUs.
if (cpus != null) {
await this.call('VM.set_VCPUs_at_startup')
}
// TODO: remove existing VDIs (to make sure there are only those
// wanted).
//
// Registers the VDIs description for the provisioner.
if (vdis.length) {
const vdisXml = formatXml({
provisition: {
disks: map(vdis, vdi => {
const bootable = String(Boolean(vdi.bootable))
const size = String(vdi.size)
const sr = vdi.sr || vdi.SR
return {$: {bootable, size, sr}}
})
}
})
// Removes any preexisting entry.
await this.call('VM.remove_from_other_config', vm.$ref, 'disks').catch(noop)
await this.call('VM.add_to_other_config', vm.$ref, 'disks', vdisXml)
}
// Removes any preexisting entry.
await this.call('VM.remove_from_other_config', vm.$ref, 'install-repository').catch(noop)
if (installRepository != null) {
await this.call('VM.add_to_other_config', vm.$ref, 'install-repository', installRepository)
}
// Creates the VDIs and executes the initial steps of the
// installation.
await this.call('VM.provision', vm.$ref)
if (installRepository != null) {
try {
const cd = this.getObject(installRepository)
await this._insertCdIntoVm(cd, vm)
} catch (_) {}
}
// Returns the last state of the new VM.
return await this._waitObject(vm.$id)
}
async deleteVm (vmId, deleteDisks = false) {
@ -503,14 +590,15 @@ export default class Xapi extends XapiBase {
)
}
async attachVdiToVm (vdiId, vmId, {
bootable = false,
mode = 'RW',
position
} = {}) {
const vdi = this.getObject(vdiId)
const vm = this.getObject(vmId)
// =================================================================
async _createVbd (vm, vdi, {
bootable = false,
position = undefined,
type = 'Disk',
readOnly = (type !== 'Disk')
}) {
// TODO: use VM.get_allowed_VBD_devices()?
if (position == null) {
forEach(vm.$VBDs, vbd => {
const curPos = +vbd.userdevice
@ -525,11 +613,12 @@ export default class Xapi extends XapiBase {
const vbdRef = await this.call('VBD.create', {
bootable,
empty: false,
mode,
mode: readOnly ? 'RO' : 'RW',
other_config: {},
qos_algorithm_params: {},
qos_algorithm_type: '',
type: 'Disk',
type,
unpluggable: (type !== 'Disk'),
userdevice: String(position),
VDI: vdi.$ref,
VM: vm.$ref
@ -538,17 +627,80 @@ export default class Xapi extends XapiBase {
if (isVmRunning(vm)) {
await this.call('VBD.plug', vbdRef)
}
return vbdRef
}
async _deleteVdi (vdiId) {
const vdi = this.getObject(vdiId)
await this.call('VDI.destroy', vdi.$ref)
}
_getVmCdDrive (vm) {
for (let vbd of vm.$VBDs) {
if (vbd.type === 'CD') {
return vbd
}
}
}
async _insertCdIntoVm (cd, vm, force) {
const cdDrive = await this._getVmCdDrive(vm)
if (cdDrive) {
try {
await this.call('VBD.insert', cdDrive.$ref, cd.$ref).catch()
} catch (error) {
if (force && error.code === 'VBD_NOT_EMPTY') {
await this.call('VBD.eject', cdDrive.$ref).catch(noop)
return this._insertCdIntoVm(cd, vm, force)
}
throw error
}
} else {
await this._createVbd(vm, cd, {
bootable: true,
type: 'CD'
})
}
}
async attachVdiToVm (vdiId, vmId, opts = undefined) {
await this._createVbd(
this.getObject(vdiId),
this.getObject(vmId),
opts
)
}
async insertCdIntoVm (cdId, vmId, force = undefined) {
await this._insertCdIntoVm(
this.getObject(cdId),
this.getObject(vmId),
force
)
}
// =================================================================
async createVirtualInterface (vmId, networkId, {
async _createVif (vm, network, {
mac = '',
mtu = 1500,
position = 0
position = undefined
} = {}) {
const vm = this.getObject(vmId)
const network = this.getObject(networkId)
// TODO: use VM.get_allowed_VIF_devices()?
if (position == null) {
forEach(vm.$VIFs, vif => {
const curPos = +vif.device
if (!(position > curPos)) {
position = curPos
}
})
position = position == null ? 0 : position + 1
}
const vifRef = await this.call('VIF.create', {
device: String(position),
@ -565,7 +717,17 @@ export default class Xapi extends XapiBase {
await this.call('VIF.plug', vifRef)
}
return await this._getOrWaitObject(vifRef)
return vifRef
}
async createVirtualInterface (vmId, networkId, opts = undefined) {
return await this._getOrWaitObject(
await this._createVif(
this.getObject(vmId),
this.getObject(networkId),
opts
)
)
}
// =================================================================