feat(VHD import): ensure uploaded file is a VHD (#5906)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
})
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user