diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 9e8920782..e474554b2 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -31,5 +31,7 @@ > > In case of conflict, the highest (lowest in previous list) `$version` wins. +- vhd-lib minor - xo-server-netbox patch +- xo-server patch - xo-web patch diff --git a/packages/vhd-lib/src/_checkFooter.js b/packages/vhd-lib/src/checkFooter.js similarity index 100% rename from packages/vhd-lib/src/_checkFooter.js rename to packages/vhd-lib/src/checkFooter.js diff --git a/packages/vhd-lib/src/createVhdStreamWithLength.js b/packages/vhd-lib/src/createVhdStreamWithLength.js index 0e050912f..35b3c3b7d 100644 --- a/packages/vhd-lib/src/createVhdStreamWithLength.js +++ b/packages/vhd-lib/src/createVhdStreamWithLength.js @@ -2,7 +2,7 @@ import assert from 'assert' import { pipeline, Transform } from 'readable-stream' import { readChunk } from '@vates/read-chunk' -import checkFooter from './_checkFooter' +import checkFooter from './checkFooter' import checkHeader from './_checkHeader' import noop from './_noop' import getFirstAndLastBlocks from './_getFirstAndLastBlocks' diff --git a/packages/vhd-lib/src/index.js b/packages/vhd-lib/src/index.js index 0aa5f21cd..64ef9c622 100644 --- a/packages/vhd-lib/src/index.js +++ b/packages/vhd-lib/src/index.js @@ -8,3 +8,4 @@ export { default as createSyntheticStream } from './createSyntheticStream' export { default as mergeVhd } from './merge' export { default as createVhdStreamWithLength } from './createVhdStreamWithLength' export { default as peekFooterFromVhdStream } from './peekFooterFromVhdStream' +export { default as checkFooter } from './checkFooter' diff --git a/packages/vhd-lib/src/vhd.js b/packages/vhd-lib/src/vhd.js index 95d5f1b6f..cdfe65df8 100644 --- a/packages/vhd-lib/src/vhd.js +++ b/packages/vhd-lib/src/vhd.js @@ -1,7 +1,7 @@ import assert from 'assert' import { createLogger } from '@xen-orchestra/log' -import checkFooter from './_checkFooter' +import checkFooter from './checkFooter' import checkHeader from './_checkHeader' import getFirstAndLastBlocks from './_getFirstAndLastBlocks' import { fuFooter, fuHeader, checksumStruct, unpackField } from './_structs' diff --git a/packages/xo-server/src/api/disk.mjs b/packages/xo-server/src/api/disk.mjs index 4e08caf57..e62ca599e 100644 --- a/packages/xo-server/src/api/disk.mjs +++ b/packages/xo-server/src/api/disk.mjs @@ -1,11 +1,12 @@ import * as multiparty from 'multiparty' +import assert from 'assert' import getStream from 'get-stream' import pump from 'pump' import { createLogger } from '@xen-orchestra/log' import { defer } from 'golike-defer' -import { format } from 'json-rpc-peer' +import { format, JsonRpcError } from 'json-rpc-peer' import { noSuchObject } from 'xo-common/api-errors.js' -import { peekFooterFromVhdStream } from 'vhd-lib' +import { checkFooter, peekFooterFromVhdStream } from 'vhd-lib' import { vmdkToVhd } from 'xo-vmdk-to-vhd' import { VDI_FORMAT_VHD } from '../xapi/index.mjs' @@ -161,44 +162,59 @@ async function handleImport(req, res, { type, name, description, vmdkData, srId, const form = new multiparty.Form() form.on('error', reject) form.on('part', async part => { - if (part.name !== 'file') { - promises.push( - (async () => { + try { + if (part.name !== 'file') { + promises.push( + (async () => { const buffer = await getStream.buffer(part) vmdkData[part.name] = new Uint32Array( buffer.buffer, buffer.byteOffset, buffer.length / Uint32Array.BYTES_PER_ELEMENT ) - })() - ) - } else { - await Promise.all(promises) - part.length = part.byteCount - if (type === 'vmdk') { - vhdStream = await vmdkToVhd(part, vmdkData.grainLogicalAddressList, vmdkData.grainFileOffsetList) - size = vmdkData.capacity - } else if (type === 'vhd') { - vhdStream = part - const footer = await peekFooterFromVhdStream(vhdStream) - size = footer.currentSize + })() + ) } else { - throw new Error(`Unknown disk type, expected "vhd" or "vmdk", got ${type}`) + await Promise.all(promises) + part.length = part.byteCount + if (type === 'vmdk') { + vhdStream = await vmdkToVhd(part, vmdkData.grainLogicalAddressList, vmdkData.grainFileOffsetList) + size = vmdkData.capacity + } else if (type === 'vhd') { + vhdStream = part + const footer = await peekFooterFromVhdStream(vhdStream) + try { + checkFooter(footer) + } catch (e) { + if (e instanceof assert.AssertionError) { + throw new JsonRpcError(`Vhd file had an invalid header ${e}`) + } + } + size = footer.currentSize + } else { + throw new JsonRpcError(`Unknown disk type, expected "vhd" or "vmdk", got ${type}`) + } + const vdi = await xapi.createVdi({ + name_description: description, + name_label: name, + size, + sr: srId, + }) + try { + await xapi.importVdiContent(vdi, vhdStream, VDI_FORMAT_VHD) + res.end(format.response(0, vdi.$id)) + } catch (e) { + await vdi.$destroy() + throw e + } + resolve() } - const vdi = await xapi.createVdi({ - name_description: description, - name_label: name, - size, - sr: srId, - }) - try { - await xapi.importVdiContent(vdi, vhdStream, VDI_FORMAT_VHD) - res.end(format.response(0, vdi.$id)) - } catch (e) { - await vdi.$destroy() - throw e - } - resolve() + } catch (e) { + res.writeHead(500) + res.end(format.error(0, new JsonRpcError(e.message))) + // destroy the reader to stop the file upload + req.destroy() + reject(e) } }) form.parse(req) diff --git a/packages/xo-web/src/common/xo/index.js b/packages/xo-web/src/common/xo/index.js index 8dc589c2e..67aa43218 100644 --- a/packages/xo-web/src/common/xo/index.js +++ b/packages/xo-web/src/common/xo/index.js @@ -4,7 +4,6 @@ import fpSortBy from 'lodash/fp/sortBy' import React from 'react' import updater from 'xoa-updater' import URL from 'url-parse' -import * as xoaPlans from 'xoa-plans' import Xo from 'xo-lib' import { createBackoff } from 'jsonrpc-websocket-client' import { get as getDefined } from '@xen-orchestra/defined' @@ -1531,12 +1530,14 @@ export const importVm = async (file, type = 'xva', data = undefined, sr) => { const { name } = file info(_('startVmImport'), name) + // eslint-disable-next-line no-undef const formData = new FormData() if (data !== undefined && data.tables !== undefined) { for (const k in data.tables) { const tables = await data.tables[k] delete data.tables[k] for (const l in tables) { + // eslint-disable-next-line no-undef const blob = new Blob([tables[l]]) formData.append(l, blob, k) } @@ -1595,11 +1596,13 @@ export const importVms = (vms, sr) => ).then(ids => ids.filter(_ => _ !== undefined)) const importDisk = async ({ description, file, name, type, vmdkData }, sr) => { + // eslint-disable-next-line no-undef const formData = new FormData() if (vmdkData !== undefined) { for (const l of ['grainLogicalAddressList', 'grainFileOffsetList']) { const table = await vmdkData[l] delete vmdkData[l] + // eslint-disable-next-line no-undef const blob = new Blob([table]) formData.append(l, blob, file.name) } @@ -1613,10 +1616,10 @@ const importDisk = async ({ description, file, name, type, vmdkData }, sr) => { }) formData.append('file', file) const result = await post(res.$sendTo, formData) - if (result.status !== 200) { - throw result.status - } const body = await result.json() + if (result.status !== 200) { + throw new Error(body.error.message) + } await body.result } @@ -1624,7 +1627,7 @@ export const importDisks = (disks, sr) => Promise.all( map(disks, disk => importDisk(disk, sr).catch(err => { - error(_('diskImportFailed'), err) + error(_('diskImportFailed'), err.message) throw err }) )