feat(xo-{server,web}): improve OVA import error reporting (#5797)

This commit is contained in:
Nicolas Raynaud 2021-06-02 16:23:08 +02:00 committed by GitHub
parent cbd650c5ef
commit 1c91fb9dd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 96 additions and 45 deletions

View File

@ -1256,12 +1256,13 @@ async function handleVmImport(req, res, { data, srId, type, xapi }) {
// Timeout seems to be broken in Node 4.
// See https://github.com/nodejs/node/issues/3319
req.setTimeout(43200000) // 12 hours
await new Promise((resolve, reject) => {
const vm = await new Promise((resolve, reject) => {
const form = new multiparty.Form()
const promises = []
const tables = {}
form.on('error', reject)
form.on('part', async part => {
try {
if (part.name !== 'file') {
promises.push(
(async () => {
@ -1281,13 +1282,16 @@ async function handleVmImport(req, res, { data, srId, type, xapi }) {
await Promise.all(promises)
// XVA files are directly sent to xcp-ng who wants a content-length
part.length = part.byteCount
const vm = await xapi.importVm(part, { data, srId, type })
res.end(format.response(0, vm.$id))
resolve()
resolve(xapi.importVm(part, { data, srId, type }))
}
} catch (e) {
// multiparty is not promise-aware, we have to chain errors ourselves.
reject(e)
}
})
form.parse(req)
})
res.end(format.response(0, vm.$id))
}
// TODO: "sr_id" can be passed in URL to target a specific SR
@ -1297,12 +1301,18 @@ async function import_({ data, sr, type }) {
}
return {
$sendTo: await this.registerHttpRequest(handleVmImport, {
$sendTo: await this.registerApiHttpRequest(
'vm.import',
this.session,
handleVmImport,
{
data,
srId: sr._xapiId,
type,
xapi: this.getXapi(sr),
}),
},
{ exposeAllErrors: true }
),
}
}

View File

@ -1102,6 +1102,9 @@ export default class Xapi extends XapiBase {
const vdis = {}
const compression = {}
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(
map(disks, async disk => {
const vdi = (vdis[disk.path] = await this.createVdi({

View File

@ -6,7 +6,7 @@ import kindOf from 'kindof'
import ms from 'ms'
import schemaInspector from 'schema-inspector'
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 sensitiveValues from '../sensitive-values.mjs'
@ -376,4 +376,47 @@ export default class Api {
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
)
}
}

View File

@ -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)
return post($sendTo, formData)
.then(res => {
const res = await post(result.$sendTo, formData)
const json = await res.json()
if (res.status !== 200) {
throw res.status
error(_('vmImportFailed'), name)
throw json.error
}
success(_('vmImportSuccess'), name)
return res.json().then(body => body.result)
})
.catch(err => {
error(_('vmImportFailed'), name)
throw err
})
})
return json.result
}
import ImportVdiModalBody from './import-vdi-modal' // eslint-disable-line import/first