Merge pull request #70 from vatesfr/vm-create-refactoring
vm.create() refactoring
This commit is contained in:
commit
b58826da6e
@ -67,6 +67,7 @@
|
||||
"lodash.map": "^3.0.0",
|
||||
"lodash.pick": "^3.0.0",
|
||||
"lodash.result": "^3.0.0",
|
||||
"lodash.snakecase": "^3.0.1",
|
||||
"lodash.startswith": "^3.0.1",
|
||||
"make-error": "^1",
|
||||
"multikey-hash": "^1.0.1",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -3,19 +3,10 @@ import {parseSize} from '../utils'
|
||||
// ===================================================================
|
||||
|
||||
export async function create ({name, size, sr}) {
|
||||
const xapi = this.getXAPI(sr)
|
||||
|
||||
const ref = await xapi.call('VDI.create', {
|
||||
name_label: name,
|
||||
other_config: {},
|
||||
read_only: false,
|
||||
sharable: false,
|
||||
SR: sr.ref,
|
||||
type: 'user',
|
||||
virtual_size: String(parseSize(size))
|
||||
const vdi = await this.getXAPI(sr).createVdi(sr.id, parseSize(size), {
|
||||
name_label: name
|
||||
})
|
||||
|
||||
return (await xapi.call('VDI.get_record', ref)).uuid
|
||||
return vdi.$id
|
||||
}
|
||||
|
||||
create.description = 'create a new disk on a SR'
|
||||
|
@ -9,12 +9,9 @@ $isArray = require 'lodash.isarray'
|
||||
#=====================================================================
|
||||
|
||||
delete_ = $coroutine ({vdi}) ->
|
||||
xapi = @getXAPI vdi
|
||||
yield @getXAPI(vdi).deleteVdi(vdi.id)
|
||||
|
||||
# TODO: check if VDI is attached before
|
||||
yield xapi.call 'VDI.destroy', vdi.ref
|
||||
|
||||
return true
|
||||
return
|
||||
|
||||
delete_.params = {
|
||||
id: { type: 'string' },
|
||||
|
@ -1,7 +1,6 @@
|
||||
// TODO: move into vm and rename to removeInterface
|
||||
async function delete_ ({vif}) {
|
||||
// TODO: check if VIF is attached before
|
||||
await this.getXAPI(vif).call('VIF.destroy', vif.ref)
|
||||
await this.getXAPI(vif).deleteVif(vif.id)
|
||||
}
|
||||
export {delete_ as delete}
|
||||
|
||||
|
@ -28,143 +28,23 @@ $isVMRunning = do ->
|
||||
#=====================================================================
|
||||
|
||||
# TODO: Implement ACLs
|
||||
# FIXME: Make the method as atomic as possible.
|
||||
create = $coroutine ({
|
||||
installation
|
||||
name_description
|
||||
name_label
|
||||
template
|
||||
VDIs
|
||||
VIFs
|
||||
}) ->
|
||||
# Gets the corresponding connection.
|
||||
xapi = @getXAPI template
|
||||
vm = yield @getXAPI(template).createVm(template.id, {
|
||||
installRepository: installation && installation.repository,
|
||||
nameDescription: name_description,
|
||||
nameLabel: name_label,
|
||||
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'
|
||||
|
||||
@ -178,8 +58,9 @@ create.params = {
|
||||
}
|
||||
}
|
||||
|
||||
# Name of the new VM.
|
||||
# Name/description of the new VM.
|
||||
name_label: { type: 'string' }
|
||||
name_description: { type: 'string', optional: true }
|
||||
|
||||
# TODO: add the install repository!
|
||||
# VBD.insert/eject
|
||||
@ -212,7 +93,6 @@ create.params = {
|
||||
items: {
|
||||
type: 'object'
|
||||
properties: {
|
||||
bootable: { type: 'boolean' }
|
||||
device: { type: 'string' }
|
||||
size: { type: 'integer' }
|
||||
SR: { type: 'string' }
|
||||
@ -280,41 +160,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 +429,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 +666,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 = {
|
||||
@ -843,7 +695,7 @@ exports.attachDisk = attachDisk
|
||||
# FIXME: position should be optional and default to last.
|
||||
|
||||
createInterface = $coroutine ({vm, network, position, mtu, mac}) ->
|
||||
vif = yield @getXAPI(vm).createVirtualInterface(vm.id, network.id, {
|
||||
vif = yield @getXAPI(vm).createVif(vm.id, network.id, {
|
||||
mac,
|
||||
mtu,
|
||||
position
|
||||
|
10
src/utils.js
10
src/utils.js
@ -39,15 +39,7 @@ export const generateToken = (function (randomBytes) {
|
||||
|
||||
export const formatXml = (function () {
|
||||
const builder = new xml2js.Builder({
|
||||
xmldec: {
|
||||
// Do not include an XML header.
|
||||
//
|
||||
// This is not how this setting should be set but due to the
|
||||
// implementation of both xml2js and xmlbuilder-js it works.
|
||||
//
|
||||
// TODO: Find a better alternative.
|
||||
headless: true
|
||||
}
|
||||
headless: true
|
||||
})
|
||||
|
||||
return (...args) => builder.buildObject(...args)
|
||||
|
@ -6,12 +6,13 @@ import expect from 'must'
|
||||
|
||||
import {
|
||||
ensureArray,
|
||||
extractProperty
|
||||
extractProperty,
|
||||
formatXml
|
||||
} from './utils'
|
||||
|
||||
// ===================================================================
|
||||
|
||||
describe('ensureArray', function () {
|
||||
describe('ensureArray()', function () {
|
||||
it('wrap the value in an array', function () {
|
||||
const value = 'foo'
|
||||
|
||||
@ -31,7 +32,7 @@ describe('ensureArray', function () {
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
describe('extractProperty', function () {
|
||||
describe('extractProperty()', function () {
|
||||
it('returns the value of the property', function () {
|
||||
const value = {}
|
||||
const obj = { prop: value }
|
||||
@ -47,3 +48,21 @@ describe('extractProperty', function () {
|
||||
expect(obj).to.not.have.property('prop')
|
||||
})
|
||||
})
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
describe('formatXml()', function () {
|
||||
it('formats a JS object to an XML string', function () {
|
||||
expect(formatXml({
|
||||
foo: {
|
||||
bar: [
|
||||
{$: {baz: 'plop'}},
|
||||
{$: {baz: 'plip'}}
|
||||
]
|
||||
}
|
||||
})).to.equal(`<foo>
|
||||
<bar baz="plop"/>
|
||||
<bar baz="plip"/>
|
||||
</foo>`)
|
||||
})
|
||||
})
|
||||
|
@ -239,11 +239,12 @@ export function vm (obj) {
|
||||
arch: otherConfig['install-arch'],
|
||||
disks: (function () {
|
||||
const {disks: xml} = otherConfig
|
||||
if (!xml) {
|
||||
let data
|
||||
if (!xml || !(data = parseXml(xml)).provision) {
|
||||
return []
|
||||
}
|
||||
|
||||
const disks = ensureArray(parseXml(xml).provision.disk)
|
||||
const disks = ensureArray(data.provision.disk)
|
||||
forEach(disks, function normalize (disk) {
|
||||
disk.bootable = disk.bootable === 'true'
|
||||
disk.size = +disk.size
|
||||
|
316
src/xapi.js
316
src/xapi.js
@ -4,6 +4,7 @@ import find from 'lodash.find'
|
||||
import forEach from 'lodash.foreach'
|
||||
import got from 'got'
|
||||
import map from 'lodash.map'
|
||||
import snakeCase from 'lodash.snakecase'
|
||||
import unzip from 'julien-f-unzip'
|
||||
import {PassThrough} from 'stream'
|
||||
import {promisify} from 'bluebird'
|
||||
@ -13,7 +14,11 @@ import {
|
||||
} from 'xen-api'
|
||||
|
||||
import {debounce} from './decorators'
|
||||
import {ensureArray, noop, parseXml, pFinally} from './utils'
|
||||
import {
|
||||
ensureArray,
|
||||
noop, parseXml,
|
||||
pFinally
|
||||
} from './utils'
|
||||
import {JsonRpcError} from './api-errors'
|
||||
|
||||
const debug = createDebug('xo:xapi')
|
||||
@ -70,6 +75,8 @@ const VM_RUNNING_POWER_STATES = {
|
||||
}
|
||||
export const isVmRunning = (vm) => VM_RUNNING_POWER_STATES[vm.power_state]
|
||||
|
||||
export const isVmHvm = (vm) => Boolean(vm.HVM_boot_policy)
|
||||
|
||||
// ===================================================================
|
||||
|
||||
export default class Xapi extends XapiBase {
|
||||
@ -124,17 +131,15 @@ export default class Xapi extends XapiBase {
|
||||
_waitObject (idOrUuidOrRef) {
|
||||
let watcher = this._objectWatchers[idOrUuidOrRef]
|
||||
if (!watcher) {
|
||||
let resolve, reject
|
||||
const promise = new Promise((resolve_, reject_) => {
|
||||
let resolve
|
||||
const promise = new Promise(resolve_ => {
|
||||
resolve = resolve_
|
||||
reject = reject_
|
||||
})
|
||||
|
||||
// Register the watcher.
|
||||
watcher = this._objectWatchers[idOrUuidOrRef] = {
|
||||
promise,
|
||||
resolve,
|
||||
reject
|
||||
resolve
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,11 +198,11 @@ export default class Xapi extends XapiBase {
|
||||
|
||||
// =================================================================
|
||||
|
||||
async _setObjectProperties (id, props) {
|
||||
async _setObjectProperties (object, props) {
|
||||
const {
|
||||
$ref: ref,
|
||||
$type: type
|
||||
} = this.getObject(id)
|
||||
} = object
|
||||
|
||||
const namespace = getNamespaceForType(type)
|
||||
|
||||
@ -205,7 +210,7 @@ export default class Xapi extends XapiBase {
|
||||
// properties that failed to be set.
|
||||
await Promise.all(map(props, (value, name) => {
|
||||
if (value != null) {
|
||||
return this.call(`${namespace}.set_${name}`, ref, value)
|
||||
return this.call(`${namespace}.set_${snakeCase(name)}`, ref, value)
|
||||
}
|
||||
}))
|
||||
}
|
||||
@ -214,7 +219,7 @@ export default class Xapi extends XapiBase {
|
||||
name_label,
|
||||
name_description
|
||||
}) {
|
||||
await this._setObjectProperties(this.pool.$id, {
|
||||
await this._setObjectProperties(this.pool, {
|
||||
name_label,
|
||||
name_description
|
||||
})
|
||||
@ -224,7 +229,7 @@ export default class Xapi extends XapiBase {
|
||||
name_label,
|
||||
name_description
|
||||
}) {
|
||||
await this._setObjectProperties(id, {
|
||||
await this._setObjectProperties(this.getObject(id), {
|
||||
name_label,
|
||||
name_description
|
||||
})
|
||||
@ -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,139 @@ 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, {
|
||||
nameDescription = undefined,
|
||||
nameLabel = undefined,
|
||||
cpus = undefined,
|
||||
installRepository = undefined,
|
||||
vdis = [],
|
||||
vifs = []
|
||||
} = {}) {
|
||||
const installMethod = (() => {
|
||||
if (installRepository == null) {
|
||||
return 'none'
|
||||
}
|
||||
|
||||
try {
|
||||
installRepository = this.getObject(installRepository)
|
||||
return 'cd'
|
||||
} catch (_) {
|
||||
return 'network'
|
||||
}
|
||||
})()
|
||||
|
||||
const template = this.getObject(templateId)
|
||||
|
||||
// Clones the template.
|
||||
const vm = await this._getOrWaitObject(
|
||||
await this._cloneVm(template, nameLabel)
|
||||
)
|
||||
|
||||
// TODO: copy BIOS strings?
|
||||
|
||||
// Removes disks from the provision XML, we will create them by
|
||||
// ourselves.
|
||||
await this.call('VM.remove_from_other_config', vm.$ref, 'disks').catch(noop)
|
||||
|
||||
// Creates the VDIs and executes the initial steps of the
|
||||
// installation.
|
||||
await this.call('VM.provision', vm.$ref)
|
||||
|
||||
// Set VMs params.
|
||||
this._setObjectProperties(vm, {
|
||||
nameDescription,
|
||||
VCPUs_at_startup: cpus
|
||||
})
|
||||
|
||||
// Sets boot parameters.
|
||||
{
|
||||
const isHvm = isVmHvm(vm)
|
||||
|
||||
if (isHvm) {
|
||||
if (!vdis.length || installMethod === 'network') {
|
||||
// TODO: set boot order
|
||||
}
|
||||
} else { // PV
|
||||
if (vm.PV_bootloader === 'eliloader') {
|
||||
// Removes any preexisting entry.
|
||||
await this.call('VM.remove_from_other_config', vm.$ref, 'install-repository').catch(noop)
|
||||
|
||||
if (installMethod === 'network') {
|
||||
// TODO: normalize RHEL URL?
|
||||
|
||||
await this.call('VM.add_to_other_config', vm.$ref, 'install-repository', installRepository)
|
||||
} else if (installMethod === 'cd') {
|
||||
await this.call('VM.add_to_other_config', vm.$ref, 'install-repository', 'cdrom')
|
||||
await this._insertCdIntoVm(installRepository, vm)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: set PV args.
|
||||
}
|
||||
}
|
||||
|
||||
const ref = this.call('VM.clone', vm.$ref, name_label)
|
||||
// Creates the VDIs.
|
||||
//
|
||||
// TODO: set vm.suspend_SR
|
||||
{
|
||||
const {$default_SR: defaultSr} = this.pool
|
||||
await Promise.all(map(vdis, (vdiDescription, i) => {
|
||||
return this._createVdi(
|
||||
this.getObject(vdiDescription.sr || vdiDescription.SR, defaultSr),
|
||||
vdiDescription.size,
|
||||
{
|
||||
name_label: vdiDescription.name_label,
|
||||
name_description: vdiDescription.name_description
|
||||
}
|
||||
)
|
||||
.then(ref => this._getOrWaitObject(ref))
|
||||
.then(vdi => this._createVbd(vm, vdi, {
|
||||
// Only the first VBD if installMethod is not cd is bootable.
|
||||
bootable: installMethod !== 'cd' && !i
|
||||
}))
|
||||
}))
|
||||
}
|
||||
|
||||
return await this._getOrWaitObject(ref)
|
||||
// Destroys the VIFs cloned from the template.
|
||||
await Promise.all(map(vm.$vifs, vif => this._deleteVif(vif)))
|
||||
|
||||
// Creates the VIFs specified by the user.
|
||||
{
|
||||
let position = 0
|
||||
await Promise.all(map(vifs, vif => this._createVif(
|
||||
vm,
|
||||
this.getObject(vif.network),
|
||||
{
|
||||
position: position++,
|
||||
mac: vif.mac,
|
||||
mtu: vif.mtu
|
||||
}
|
||||
)))
|
||||
}
|
||||
|
||||
// TODO: Create Cloud config drives.
|
||||
|
||||
// TODO: Assign VGPUs.
|
||||
|
||||
return vm
|
||||
}
|
||||
|
||||
async deleteVm (vmId, deleteDisks = false) {
|
||||
@ -439,6 +567,11 @@ export default class Xapi extends XapiBase {
|
||||
|
||||
if (deleteDisks) {
|
||||
await Promise.all(map(vm.$VBDs, vbd => {
|
||||
// Do not delete unpluggable VDIs.
|
||||
if (vbd.unpluggable) {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
return this._deleteVdi(vbd.$VDI).catch(noop)
|
||||
} catch (_) {}
|
||||
@ -503,33 +636,27 @@ export default class Xapi extends XapiBase {
|
||||
)
|
||||
}
|
||||
|
||||
async attachVdiToVm (vdiId, vmId, {
|
||||
// =================================================================
|
||||
|
||||
async _createVbd (vm, vdi, {
|
||||
bootable = false,
|
||||
mode = 'RW',
|
||||
position
|
||||
} = {}) {
|
||||
const vdi = this.getObject(vdiId)
|
||||
const vm = this.getObject(vmId)
|
||||
|
||||
position = undefined,
|
||||
type = 'Disk',
|
||||
readOnly = (type !== 'Disk')
|
||||
}) {
|
||||
if (position == null) {
|
||||
forEach(vm.$VBDs, vbd => {
|
||||
const curPos = +vbd.userdevice
|
||||
if (!(position > curPos)) {
|
||||
position = curPos
|
||||
}
|
||||
})
|
||||
|
||||
position = position == null ? 0 : position + 1
|
||||
position = (await this.call('VM.get_allowed_VBD_devices', vm.$ref))[0]
|
||||
}
|
||||
|
||||
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 +665,105 @@ export default class Xapi extends XapiBase {
|
||||
if (isVmRunning(vm)) {
|
||||
await this.call('VBD.plug', vbdRef)
|
||||
}
|
||||
|
||||
return vbdRef
|
||||
}
|
||||
|
||||
async _createVdi (sr, size, {
|
||||
name_label = '',
|
||||
name_description = undefined
|
||||
} = {}) {
|
||||
return await this.call('VDI.create', {
|
||||
name_label: name_label,
|
||||
name_description: name_description,
|
||||
other_config: {},
|
||||
read_only: false,
|
||||
sharable: false,
|
||||
SR: sr.$ref,
|
||||
type: 'user',
|
||||
virtual_size: String(size)
|
||||
})
|
||||
}
|
||||
|
||||
// TODO: check whether the VDI is attached.
|
||||
async _deleteVdi (vdi) {
|
||||
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 createVdi (srId, size, opts) {
|
||||
return await this._getOrWaitObject(
|
||||
await this._createVdi(this.getObject(srId), size, opts)
|
||||
)
|
||||
}
|
||||
|
||||
async deleteVdi (vdiId) {
|
||||
await this._deleteVdi(this.getObject(vdiId))
|
||||
}
|
||||
|
||||
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 +780,26 @@ export default class Xapi extends XapiBase {
|
||||
await this.call('VIF.plug', vifRef)
|
||||
}
|
||||
|
||||
return await this._getOrWaitObject(vifRef)
|
||||
return vifRef
|
||||
}
|
||||
|
||||
// TODO: check whether the VIF was unplugged before.
|
||||
async _deleteVif (vif) {
|
||||
await this.call('VIF.destroy', vif.$ref)
|
||||
}
|
||||
|
||||
async createVif (vmId, networkId, opts = undefined) {
|
||||
return await this._getOrWaitObject(
|
||||
await this._createVif(
|
||||
this.getObject(vmId),
|
||||
this.getObject(networkId),
|
||||
opts
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
async deleteVif (vifId) {
|
||||
await this._deleteVif(this.getObject(vifId))
|
||||
}
|
||||
|
||||
// =================================================================
|
||||
|
Loading…
Reference in New Issue
Block a user