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.
|
||||
// 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 => {
|
||||
if (part.name !== 'file') {
|
||||
promises.push(
|
||||
(async () => {
|
||||
if (!(part.filename in tables)) {
|
||||
tables[part.filename] = {}
|
||||
}
|
||||
const view = new DataView((await getStream.buffer(part)).buffer)
|
||||
const result = new Uint32Array(view.byteLength / 4)
|
||||
for (const i in result) {
|
||||
result[i] = view.getUint32(i * 4, true)
|
||||
}
|
||||
tables[part.filename][part.name] = result
|
||||
data.tables = tables
|
||||
})()
|
||||
)
|
||||
} else {
|
||||
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()
|
||||
try {
|
||||
if (part.name !== 'file') {
|
||||
promises.push(
|
||||
(async () => {
|
||||
if (!(part.filename in tables)) {
|
||||
tables[part.filename] = {}
|
||||
}
|
||||
const view = new DataView((await getStream.buffer(part)).buffer)
|
||||
const result = new Uint32Array(view.byteLength / 4)
|
||||
for (const i in result) {
|
||||
result[i] = view.getUint32(i * 4, true)
|
||||
}
|
||||
tables[part.filename][part.name] = result
|
||||
data.tables = tables
|
||||
})()
|
||||
)
|
||||
} else {
|
||||
await Promise.all(promises)
|
||||
// XVA files are directly sent to xcp-ng who wants a content-length
|
||||
part.length = part.byteCount
|
||||
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, {
|
||||
data,
|
||||
srId: sr._xapiId,
|
||||
type,
|
||||
xapi: this.getXapi(sr),
|
||||
}),
|
||||
$sendTo: await this.registerApiHttpRequest(
|
||||
'vm.import',
|
||||
this.session,
|
||||
handleVmImport,
|
||||
{
|
||||
data,
|
||||
srId: sr._xapiId,
|
||||
type,
|
||||
xapi: this.getXapi(sr),
|
||||
},
|
||||
{ exposeAllErrors: true }
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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({
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -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 }) => {
|
||||
formData.append('file', file)
|
||||
return post($sendTo, formData)
|
||||
.then(res => {
|
||||
if (res.status !== 200) {
|
||||
throw res.status
|
||||
}
|
||||
success(_('vmImportSuccess'), name)
|
||||
return res.json().then(body => body.result)
|
||||
})
|
||||
.catch(err => {
|
||||
error(_('vmImportFailed'), name)
|
||||
throw err
|
||||
})
|
||||
})
|
||||
const result = await _call('vm.import', { type, data, sr: resolveId(sr) })
|
||||
formData.append('file', file)
|
||||
const res = await post(result.$sendTo, formData)
|
||||
const json = await res.json()
|
||||
if (res.status !== 200) {
|
||||
error(_('vmImportFailed'), name)
|
||||
throw json.error
|
||||
}
|
||||
success(_('vmImportSuccess'), name)
|
||||
return json.result
|
||||
}
|
||||
|
||||
import ImportVdiModalBody from './import-vdi-modal' // eslint-disable-line import/first
|
||||
|
Loading…
Reference in New Issue
Block a user