feat(xen-api/{get,put}Resource): add inactivity detection (#4090)

This commit is contained in:
Julien Fontanet 2019-03-28 13:55:56 +01:00 committed by GitHub
parent e44dbfb2a4
commit b47e097983
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -134,6 +134,7 @@ export class Xapi extends EventEmitter {
this._allowUnauthorized = opts.allowUnauthorized this._allowUnauthorized = opts.allowUnauthorized
this._callTimeout = makeCallSetting(opts.callTimeout, 0) this._callTimeout = makeCallSetting(opts.callTimeout, 0)
this._httpInactivityTimeout = opts.httpInactivityTimeout ?? 5 * 60 * 1e3 // 5 mins
this._pool = null this._pool = null
this._readOnly = Boolean(opts.readOnly) this._readOnly = Boolean(opts.readOnly)
this._RecordsByType = createObject(null) this._RecordsByType = createObject(null)
@ -499,103 +500,107 @@ export class Xapi extends EventEmitter {
} }
@cancelable @cancelable
getResource($cancelToken, pathname, { host, query, task } = {}) { async getResource($cancelToken, pathname, { host, query, task } = {}) {
return this._autoTask(task, `Xapi#getResource ${pathname}`).then( const taskRef = await this._autoTask(task, `Xapi#getResource ${pathname}`)
taskRef => {
query = { ...query, session_id: this.sessionId } query = { ...query, session_id: this.sessionId }
let taskResult
let pTaskResult
if (taskRef !== undefined) { if (taskRef !== undefined) {
query.task_id = taskRef query.task_id = taskRef
taskResult = this.watchTask(taskRef) pTaskResult = this.watchTask(taskRef)
if (typeof $cancelToken.addHandler === 'function') { if (typeof $cancelToken.addHandler === 'function') {
$cancelToken.addHandler(() => taskResult) $cancelToken.addHandler(() => pTaskResult)
} }
} }
let promise = pTimeout.call( const response = await httpRequest(
httpRequest(
$cancelToken, $cancelToken,
this._url, this._url,
host && { host !== undefined && {
hostname: this.getObject(host).address, hostname: this.getObject(host).address,
}, },
{ {
pathname, pathname,
query, query,
rejectUnauthorized: !this._allowUnauthorized, rejectUnauthorized: !this._allowUnauthorized,
// this is an inactivity timeout (unclear in Node doc)
timeout: this._httpInactivityTimeout,
} }
),
HTTP_TIMEOUT
) )
if (taskResult !== undefined) { if (pTaskResult !== undefined) {
promise = promise.then(response => { response.task = pTaskResult
response.task = taskResult }
return response return response
})
}
return promise
}
)
} }
@cancelable @cancelable
putResource($cancelToken, body, pathname, { host, query, task } = {}) { async putResource($cancelToken, body, pathname, { host, query, task } = {}) {
if (this._readOnly) { if (this._readOnly) {
return Promise.reject( throw new Error('cannot put resource in read only mode')
new Error(new Error('cannot put resource in read only mode'))
)
} }
return this._autoTask(task, `Xapi#putResource ${pathname}`).then( const taskRef = await this._autoTask(task, `Xapi#putResource ${pathname}`)
taskRef => {
query = { ...query, session_id: this.sessionId } query = { ...query, session_id: this.sessionId }
let taskResult let pTaskResult
if (taskRef !== undefined) { if (taskRef !== undefined) {
query.task_id = taskRef query.task_id = taskRef
taskResult = this.watchTask(taskRef) pTaskResult = this.watchTask(taskRef)
if (typeof $cancelToken.addHandler === 'function') { if (typeof $cancelToken.addHandler === 'function') {
$cancelToken.addHandler(() => taskResult) $cancelToken.addHandler(() => pTaskResult)
} }
} }
const headers = {} const headers = {}
// Xen API does not support chunk encoding. // XAPI does not support chunk encoding so there is no proper way to send
// data without knowing its length
//
// as a work-around, a huge content length (1PiB) is added (so that the
// server won't prematurely cut the connection), and the connection will be
// cut once all the data has been sent without waiting for a response
const isStream = typeof body.pipe === 'function' const isStream = typeof body.pipe === 'function'
const { length } = body const useHack = isStream && body.length === undefined
if (isStream && length === undefined) { if (useHack) {
// add a fake huge content length (1 PiB) console.warn(
this._humanId,
'Xapi#putResource',
pathname,
'missing length'
)
headers['content-length'] = '1125899906842624' headers['content-length'] = '1125899906842624'
} }
const doRequest = (...opts) => const doRequest = httpRequest.put.bind(
pTimeout.call( undefined,
httpRequest.put(
$cancelToken, $cancelToken,
this._url, this._url,
host && { host !== undefined && {
hostname: this.getObject(host).address, hostname: this.getObject(host).address,
}, },
{ {
body, body,
headers, headers,
query,
pathname, pathname,
query,
rejectUnauthorized: !this._allowUnauthorized, rejectUnauthorized: !this._allowUnauthorized,
},
...opts // this is an inactivity timeout (unclear in Node doc)
), timeout: this._httpInactivityTimeout,
HTTP_TIMEOUT }
) )
// if a stream, sends a dummy request to probe for a // if body is a stream, sends a dummy request to probe for a redirection
// redirection before consuming body // before consuming body
const promise = isStream const response = await (isStream
? doRequest({ ? doRequest({
body: '', body: '',
@ -626,30 +631,28 @@ export class Xapi extends EventEmitter {
throw error throw error
} }
) )
: doRequest() : doRequest())
return promise.then(response => { if (pTaskResult !== undefined) {
const { req } = response pTaskResult = pTaskResult.catch(error => {
if (taskResult !== undefined) {
taskResult = taskResult.catch(error => {
error.url = response.url error.url = response.url
throw error throw error
}) })
} }
if (req.finished) { if (!useHack) {
response.cancel() // consume the response
return taskResult response.resume()
return pTaskResult
} }
return fromEvents(req, ['close', 'finish']).then(() => { const { req } = response
response.cancel() if (!req.finished) {
return taskResult await fromEvents(req, ['close', 'finish'])
})
})
} }
) response.cancel()
return pTaskResult
} }
watchTask(ref) { watchTask(ref) {