diff --git a/src/api/vm.coffee b/src/api/vm.coffee index 063865d3f..2830b75ea 100644 --- a/src/api/vm.coffee +++ b/src/api/vm.coffee @@ -239,28 +239,8 @@ exports.create = create #--------------------------------------------------------------------- -delete_ = $coroutine ({vm, delete_disks: deleteDisks}) -> - if $isVMRunning vm - @throw 'INVALID_PARAMS', 'The VM can only be deleted when halted' - - xapi = @getXAPI vm - - if deleteDisks - $forEach vm.$VBDs, (ref) => - try - VBD = @getObject ref, 'VBD' - catch e - return - - return if VBD.read_only or not VBD.VDI? - - $wait xapi.call 'VDI.destroy', VBD.VDI - - return - - $wait xapi.call 'VM.destroy', vm.ref - - return true +delete_ = ({vm, delete_disks: deleteDisks}) -> + return @getXAPI(vm).deleteVm(vm.id, deleteDisks) delete_.params = { id: { type: 'string' } @@ -819,17 +799,19 @@ export_ = $coroutine ({vm, compress}) -> ) pFinally( xapi._watchTask(task) - .then (result) -> + .then((result) -> $debug 'export succeeded' return - .catch (error) -> + ) + .catch((error) -> $debug 'export failed: %j', error return + ) , - $coroutine => + -> if snapshotRef? $debug 'deleting temp snapshot...' - $wait exports.delete.call this, id: snapshotRef, delete_disks: true + xapi.deleteVm(snapshotRef, true) return ) diff --git a/src/utils.js b/src/utils.js index 2fb3e42cb..a1b939c9b 100644 --- a/src/utils.js +++ b/src/utils.js @@ -68,6 +68,13 @@ export const parseXml = (function () { // ------------------------------------------------------------------- +// This function does nothing and returns undefined. +// +// It is often used to swallow promise's errors. +export function noop () {} + +// ------------------------------------------------------------------- + // Ponyfill for Promise.finally(cb) export const pFinally = (promise, cb) => { return promise.then( diff --git a/src/xapi.js b/src/xapi.js index e6e64609a..fd851204f 100644 --- a/src/xapi.js +++ b/src/xapi.js @@ -9,7 +9,7 @@ import {promisify} from 'bluebird' import {Xapi as XapiBase} from 'xen-api' import {debounce} from './decorators' -import {ensureArray, parseXml, pFinally} from './utils' +import {ensureArray, noop, parseXml, pFinally} from './utils' import {JsonRpcError} from './api-errors' const debug = createDebug('xo:xapi') @@ -61,6 +61,14 @@ const getNamespaceForType = (type) => typeToNamespace[type] || type // =================================================================== +const VM_RUNNING_POWER_STATES = { + Running: true, + Paused: true +} +const isVmRunning = (vm) => VM_RUNNING_POWER_STATES[vm.power_state] + +// =================================================================== + export default class Xapi extends XapiBase { constructor (...args) { super(...args) @@ -454,6 +462,33 @@ export default class Xapi extends XapiBase { // ================================================================= + async _deleteVdi (vdiId) { + const vdi = this.getObject(vdiId) + + await this.call('VDI.destroy', vdi.$ref) + } + + async deleteVm (vmId, deleteDisks = false) { + const vm = this.getObject(vmId) + + if (isVmRunning(vm)) { + throw new Error('running VMs cannot be deleted') + } + + if (deleteDisks) { + // TODO: simplify when we start to use xen-api >= 0.5 + await Promise.all(map(vm.VBDs, ref => { + try { + return this._deleteVdi(this.getObject(ref).VDI).catch(noop) + } catch (_) {} + })) + } + + await this.call('VM.destroy', vm.$ref) + } + + // ================================================================= + async _doDockerAction (vmId, action, containerId) { const vm = this.getObject(vmId) const host = this.getObject(vm.resident_on)