diff --git a/src/api/vm.coffee b/src/api/vm.coffee index 2830b75ea..ca1687064 100644 --- a/src/api/vm.coffee +++ b/src/api/vm.coffee @@ -637,7 +637,8 @@ exports.convert = convert #--------------------------------------------------------------------- snapshot = $coroutine ({vm, name}) -> - return $wait @getXAPI(vm).call 'VM.snapshot', vm.ref, name + snapshot = $wait @getXAPI(vm).snapshotVm(vm.ref, name) + return snapshot.$id snapshot.params = { id: { type: 'string' } @@ -768,68 +769,24 @@ exports.revert = revert #--------------------------------------------------------------------- +handleExport = (req, res, {stream, response: upstream}) -> + res.writeHead( + upstream.statusCode, + upstream.statusMessage ? '', + upstream.headers + ) + stream.pipe(res) + return + # TODO: integrate in xapi.js export_ = $coroutine ({vm, compress}) -> - compress ?= true - - xapi = @getXAPI vm - - # if the VM is running, we can't export it directly - # that's why we export the snapshot - exportRef = if vm.power_state is 'Running' - $debug 'VM is running, creating temp snapshot...' - snapshotRef = $wait xapi.call 'VM.snapshot', vm.ref, vm.name_label - # convert the template to a VM - $wait xapi.call 'VM.set_is_a_template', snapshotRef, false - - snapshotRef - else - vm.ref - - host = @getObject vm.$container - do (type = host.type) => - if type is 'pool' - host = @getObject host.master, 'host' - else unless type is 'host' - throw new Error "unexpected type: got #{type} instead of host" - - task = $wait xapi._createTask( - 'VM export via Xen Orchestra', - 'Export VM ' + vm.name_label - ) - pFinally( - xapi._watchTask(task) - .then((result) -> - $debug 'export succeeded' - return - ) - .catch((error) -> - $debug 'export failed: %j', error - return - ) - , - -> - if snapshotRef? - $debug 'deleting temp snapshot...' - xapi.deleteVm(snapshotRef, true) - - return - ) - - url = $wait @registerProxyRequest { - method: 'get' - hostname: host.address - pathname: '/export/' - query: { - session_id: xapi.sessionId - ref: exportRef - task_id: task.$ref - use_compression: if compress then 'true' else false - } - } + stream = $wait @getXAPI(vm).exportVm(vm.id, compress ? true) return { - $getFrom: url + $getFrom: $wait @registerHttpRequest(handleExport, { + stream, + $wait stream.response + }) } export_.params = { diff --git a/src/xapi.js b/src/xapi.js index 7a750da97..5c40b1ff6 100644 --- a/src/xapi.js +++ b/src/xapi.js @@ -211,8 +211,8 @@ export default class Xapi extends XapiBase { // Create a task. // // Returns the task object from the Xapi. - async _createTask (name, description = '') { - const ref = await this.call('task.create', name, description) + async _createTask (name = 'untitled task', description = '') { + const ref = await this.call('task.create', `[XO] ${name}`, description) debug('task created: %s', name) pFinally(this._watchTask(ref), () => { @@ -393,7 +393,7 @@ export default class Xapi extends XapiBase { // ----------------------------------------------------------------- async uploadPoolPatch (stream, length) { - const task = await this._createTask('Patch upload from XO') + const task = await this._createTask('Patch upload') // TODO: Update when xen-api >= 0.5 const poolMaster = this.objects.all[this._refsToUuids[this.pool.master]] @@ -472,6 +472,15 @@ export default class Xapi extends XapiBase { await this.call('VDI.destroy', vdi.$ref) } + async _snapshotVm (vm, nameLabel = vm.name_label) { + const ref = await this.call('VM.snapshot', vm.$ref, nameLabel) + + // Convert the template to a VM. + await this.call('VM.set_is_a_template', ref, false) + + return ref + } + async deleteVm (vmId, deleteDisks = false) { const vm = this.getObject(vmId) @@ -491,6 +500,50 @@ export default class Xapi extends XapiBase { await this.call('VM.destroy', vm.$ref) } + // Returns a stream to the exported VM. + async exportVm (vmId, {compress = true} = {}) { + const vm = this.getObject(vmId) + + let host + let snapshotRef + if (isVmRunning(vm)) { + host = this.getObject(vm.resident_on) + snapshotRef = await this._snapshotVm(vm) + } else { + host = this.getObject(this.pool.master) + } + + const task = await this._createTask('VM Snapshot', vm.name_label) + pFinally(this._watchTask(task), () => { + if (snapshotRef) { + this.deleteVm(snapshotRef, true) + } + }) + + const stream = got({ + hostname: host.address, + path: '/export/' + }, { + query: { + ref: snapshotRef || vm.$ref, + session_id: this.sessionId, + task_id: task.$ref, + use_compression: compress ? 'true' : 'false' + } + }) + stream.response = eventToPromise(stream, 'response') + + return stream + } + + async snapshotVm (vmId) { + return await this._getOrWaitObject( + await this._snapshotVm( + this.getObject(vmId) + ) + ) + } + // ================================================================= async _doDockerAction (vmId, action, containerId) {