vm.create() refactoring.
This commit is contained in:
parent
6d87a1a604
commit
db6d48f8ca
@ -7,6 +7,7 @@ export {
|
||||
InvalidJson,
|
||||
InvalidParameters,
|
||||
InvalidRequest,
|
||||
JsonRpcError,
|
||||
MethodNotFound
|
||||
} from 'json-rpc-protocol'
|
||||
|
||||
|
16
src/api.js
16
src/api.js
@ -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
|
||||
}
|
||||
|
@ -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 = {
|
||||
|
208
src/xapi.js
208
src/xapi.js
@ -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
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
Loading…
Reference in New Issue
Block a user