feat(VHD import): ensure uploaded file is a VHD (#5906)

This commit is contained in:
Nicolas Raynaud
2021-09-21 16:25:50 +02:00
committed by GitHub
parent 0966efb7f2
commit ffb6a8fa3f
7 changed files with 61 additions and 39 deletions

View File

@@ -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

View File

@@ -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'

View File

@@ -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'

View File

@@ -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'

View File

@@ -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)

View File

@@ -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
})
)