feat(xo-{server,web}): improve OVA import error reporting (#5797)
This commit is contained in:
parent
cbd650c5ef
commit
1c91fb9dd5
@ -1256,38 +1256,42 @@ async function handleVmImport(req, res, { data, srId, type, xapi }) {
|
|||||||
// Timeout seems to be broken in Node 4.
|
// Timeout seems to be broken in Node 4.
|
||||||
// See https://github.com/nodejs/node/issues/3319
|
// See https://github.com/nodejs/node/issues/3319
|
||||||
req.setTimeout(43200000) // 12 hours
|
req.setTimeout(43200000) // 12 hours
|
||||||
await new Promise((resolve, reject) => {
|
const vm = await new Promise((resolve, reject) => {
|
||||||
const form = new multiparty.Form()
|
const form = new multiparty.Form()
|
||||||
const promises = []
|
const promises = []
|
||||||
const tables = {}
|
const tables = {}
|
||||||
form.on('error', reject)
|
form.on('error', reject)
|
||||||
form.on('part', async part => {
|
form.on('part', async part => {
|
||||||
if (part.name !== 'file') {
|
try {
|
||||||
promises.push(
|
if (part.name !== 'file') {
|
||||||
(async () => {
|
promises.push(
|
||||||
if (!(part.filename in tables)) {
|
(async () => {
|
||||||
tables[part.filename] = {}
|
if (!(part.filename in tables)) {
|
||||||
}
|
tables[part.filename] = {}
|
||||||
const view = new DataView((await getStream.buffer(part)).buffer)
|
}
|
||||||
const result = new Uint32Array(view.byteLength / 4)
|
const view = new DataView((await getStream.buffer(part)).buffer)
|
||||||
for (const i in result) {
|
const result = new Uint32Array(view.byteLength / 4)
|
||||||
result[i] = view.getUint32(i * 4, true)
|
for (const i in result) {
|
||||||
}
|
result[i] = view.getUint32(i * 4, true)
|
||||||
tables[part.filename][part.name] = result
|
}
|
||||||
data.tables = tables
|
tables[part.filename][part.name] = result
|
||||||
})()
|
data.tables = tables
|
||||||
)
|
})()
|
||||||
} else {
|
)
|
||||||
await Promise.all(promises)
|
} else {
|
||||||
// XVA files are directly sent to xcp-ng who wants a content-length
|
await Promise.all(promises)
|
||||||
part.length = part.byteCount
|
// XVA files are directly sent to xcp-ng who wants a content-length
|
||||||
const vm = await xapi.importVm(part, { data, srId, type })
|
part.length = part.byteCount
|
||||||
res.end(format.response(0, vm.$id))
|
resolve(xapi.importVm(part, { data, srId, type }))
|
||||||
resolve()
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// multiparty is not promise-aware, we have to chain errors ourselves.
|
||||||
|
reject(e)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
form.parse(req)
|
form.parse(req)
|
||||||
})
|
})
|
||||||
|
res.end(format.response(0, vm.$id))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: "sr_id" can be passed in URL to target a specific SR
|
// TODO: "sr_id" can be passed in URL to target a specific SR
|
||||||
@ -1297,12 +1301,18 @@ async function import_({ data, sr, type }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
$sendTo: await this.registerHttpRequest(handleVmImport, {
|
$sendTo: await this.registerApiHttpRequest(
|
||||||
data,
|
'vm.import',
|
||||||
srId: sr._xapiId,
|
this.session,
|
||||||
type,
|
handleVmImport,
|
||||||
xapi: this.getXapi(sr),
|
{
|
||||||
}),
|
data,
|
||||||
|
srId: sr._xapiId,
|
||||||
|
type,
|
||||||
|
xapi: this.getXapi(sr),
|
||||||
|
},
|
||||||
|
{ exposeAllErrors: true }
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1102,6 +1102,9 @@ export default class Xapi extends XapiBase {
|
|||||||
const vdis = {}
|
const vdis = {}
|
||||||
const compression = {}
|
const compression = {}
|
||||||
const vifDevices = await this.call('VM.get_allowed_VIF_devices', vm.$ref)
|
const vifDevices = await this.call('VM.get_allowed_VIF_devices', vm.$ref)
|
||||||
|
if (networks.length > vifDevices.length) {
|
||||||
|
throw operationFailed({ objectId: vm.id, code: 'TOO_MANY_VIFs' })
|
||||||
|
}
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
map(disks, async disk => {
|
map(disks, async disk => {
|
||||||
const vdi = (vdis[disk.path] = await this.createVdi({
|
const vdi = (vdis[disk.path] = await this.createVdi({
|
||||||
|
@ -6,7 +6,7 @@ import kindOf from 'kindof'
|
|||||||
import ms from 'ms'
|
import ms from 'ms'
|
||||||
import schemaInspector from 'schema-inspector'
|
import schemaInspector from 'schema-inspector'
|
||||||
import { getBoundPropertyDescriptor } from 'bind-property-descriptor'
|
import { getBoundPropertyDescriptor } from 'bind-property-descriptor'
|
||||||
import { MethodNotFound } from 'json-rpc-peer'
|
import { format, JsonRpcError, MethodNotFound } from 'json-rpc-peer'
|
||||||
|
|
||||||
import * as methods from '../api/index.mjs'
|
import * as methods from '../api/index.mjs'
|
||||||
import * as sensitiveValues from '../sensitive-values.mjs'
|
import * as sensitiveValues from '../sensitive-values.mjs'
|
||||||
@ -376,4 +376,47 @@ export default class Api {
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerApiHttpRequest(method, session, fn, data, { exposeAllErrors = false, ...opts } = {}) {
|
||||||
|
const app = this._app
|
||||||
|
const logger = this._logger
|
||||||
|
return app.registerHttpRequest(
|
||||||
|
async function (req, res) {
|
||||||
|
const timestamp = Date.now()
|
||||||
|
try {
|
||||||
|
return await fn.apply(this, arguments)
|
||||||
|
} catch (error) {
|
||||||
|
const userId = session.get('user_id', undefined)
|
||||||
|
const user = userId && (await app.getUser(userId))
|
||||||
|
logger.error(`handleVmImport =!> ${error}`, {
|
||||||
|
callId: Math.random().toString(36).slice(2),
|
||||||
|
// userId,
|
||||||
|
userName: user?.email ?? '(unknown user)',
|
||||||
|
userIp: session.get('user_ip', undefined),
|
||||||
|
method: `HTTP handler of ${method}`,
|
||||||
|
timestamp,
|
||||||
|
duration: Date.now() - timestamp,
|
||||||
|
error: serializeError(error),
|
||||||
|
})
|
||||||
|
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.statusCode = 500
|
||||||
|
res.setHeader('Content-Type', 'application/json')
|
||||||
|
|
||||||
|
res.write(
|
||||||
|
format.error(
|
||||||
|
0,
|
||||||
|
exposeAllErrors && error != null && typeof error.toJsonRpcError !== 'function'
|
||||||
|
? new JsonRpcError(error.message, error.code, error.data)
|
||||||
|
: error
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
res.end()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data,
|
||||||
|
opts
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1465,21 +1465,16 @@ export const importVm = async (file, type = 'xva', data = undefined, sr) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return _call('vm.import', { type, data, sr: resolveId(sr) }).then(async ({ $sendTo }) => {
|
const result = await _call('vm.import', { type, data, sr: resolveId(sr) })
|
||||||
formData.append('file', file)
|
formData.append('file', file)
|
||||||
return post($sendTo, formData)
|
const res = await post(result.$sendTo, formData)
|
||||||
.then(res => {
|
const json = await res.json()
|
||||||
if (res.status !== 200) {
|
if (res.status !== 200) {
|
||||||
throw res.status
|
error(_('vmImportFailed'), name)
|
||||||
}
|
throw json.error
|
||||||
success(_('vmImportSuccess'), name)
|
}
|
||||||
return res.json().then(body => body.result)
|
success(_('vmImportSuccess'), name)
|
||||||
})
|
return json.result
|
||||||
.catch(err => {
|
|
||||||
error(_('vmImportFailed'), name)
|
|
||||||
throw err
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
import ImportVdiModalBody from './import-vdi-modal' // eslint-disable-line import/first
|
import ImportVdiModalBody from './import-vdi-modal' // eslint-disable-line import/first
|
||||||
|
Loading…
Reference in New Issue
Block a user