diff --git a/src/api/vm.coffee b/src/api/vm.coffee index f41b799b3..3c7920c6c 100644 --- a/src/api/vm.coffee +++ b/src/api/vm.coffee @@ -175,14 +175,8 @@ exports.insertCd = insertCd #--------------------------------------------------------------------- migrate = $coroutine ({vm, host}) -> - unless $isVMRunning vm - @throw 'INVALID_PARAMS', 'The VM can only be migrated when running' - - xapi = @getXAPI vm - - yield xapi.call 'VM.pool_migrate', vm.ref, host.ref, {'force': 'true'} - - return true + yield @getXAPI(vm).migrateVm(vm.id, @getXAPI(host), host.id) + return migrate.params = { # Identifier of the VM to migrate. @@ -202,62 +196,18 @@ exports.migrate = migrate #--------------------------------------------------------------------- migratePool = $coroutine ({ - vm: VM, + vm, host - sr: SR + sr network migrationNetwork }) -> - # TODO: map multiple VDI and VIF - - # Optional parameters - # if no network given, try to use the management network - unless network - PIF = $findWhere (@getObjects host.$PIFs), management: true - network = @getObject PIF.$network, 'network' - - # if no migrationNetwork, use the network - migrationNetwork ?= network - - # if no sr is given, try to find the default Pool SR - unless SR - pool = @getObject host.poolRef, 'pool' - target_sr_id = pool.default_SR - SR = @getObject target_sr_id, 'SR' - - unless $isVMRunning VM - @throw 'INVALID_PARAMS', 'The VM can only be migrated when running' - - vdiMap = {} - for vbdId in VM.$VBDs - VBD = @getObject vbdId, 'VBD' - continue if VBD.is_cd_drive - VDI = @getObject VBD.VDI, 'VDI' - vdiMap[VDI.ref] = SR.ref - - vifMap = {} - for vifId in VM.VIFs - VIF = @getObject vifId, 'VIF' - vifMap[VIF.ref] = network.ref - - token = yield (@getXAPI host).call( - 'host.migrate_receive' - host.ref - migrationNetwork.ref - {} # Other parameters - ) - - yield (@getXAPI VM).call( - 'VM.migrate_send' - VM.ref - token - true # Live migration - vdiMap - vifMap - {'force': 'true'} # Force migration even if CPUs are different - ) - - return true + yield @getXAPI(vm).migrateVm(vm.id, @getXAPI(host), host.id, { + migrationNetworkId: migrationNetwork?.id + networkId: network?.id, + srId: sr?.id, + }) + return migratePool.params = { diff --git a/src/xapi.js b/src/xapi.js index 7c19d4b12..9358887e6 100644 --- a/src/xapi.js +++ b/src/xapi.js @@ -662,6 +662,88 @@ export default class Xapi extends XapiBase { return stream } + async _migrateVMWithStorageMotion (vm, hostXapi, host, { + migrationNetwork = find(host.$PIFs, pif => pif.management).$network, // TODO: handle not found + sr = host.$pool.$default_SR, // TODO: handle not found + vifsMap = {} + }) { + const vdis = {} + for (const vbd of vm.$VBDs) { + if (vbd.type !== 'CD') { + vdis[vbd.$VDI.$ref] = sr.$ref + } + } + + const token = await hostXapi.call( + 'host.migrate_receive', + host.$ref, + migrationNetwork.$ref, + {} + ) + + await this.call( + 'VM.migrate_send', + vm.$ref, + token, + true, // Live migration. + vdis, + vifsMap, + { + force: 'true' + } + ) + } + + async migrateVm (vmId, hostXapi, hostId, { + migrationNetworkId, + networkId, + srId + } = {}) { + const vm = this.getObject(vmId) + if (!isVmRunning(vm)) { + throw new Error('cannot migrate a non-running VM') + } + + const host = hostXapi.getObject(hostId) + + const accrossPools = vm.$pool !== host.$pool + const useStorageMotion = ( + accrossPools || + migrationNetworkId || + networkId || + srId + ) + + if (useStorageMotion) { + const vifsMap = {} + if (accrossPools || networkId) { + const {$ref: networkRef} = networkId + ? this.getObject(networkId) + : find(host.$PIFs, pif => pif.management).$network + for (const vif of vm.$VIFs) { + vifsMap[vif.$ref] = networkRef + } + } + + await this._migrateVMWithStorageMotion(vm, hostXapi, host, { + migrationNetwork: migrationNetworkId && this.getObject(migrationNetworkId), + sr: srId && this.getObject(srId), + vifsMap + }) + } else { + try { + await this.call('VM.pool_migrate', vm.$ref, host.$ref, { force: 'true' }) + } catch (error) { + if (error.code !== 'VM_REQUIRES_SR') { + throw error + } + + // Retry using motion storage. + await this._migrateVMWithStorageMotion(vm, hostXapi, host, {}) + } + } + } + async snapshotVm (vmId) { return await this._getOrWaitObject( await this._snapshotVm(