Compare commits

...

10 Commits

Author SHA1 Message Date
b-Nollet
1653f730c9 WIP 2023-11-30 15:22:34 +01:00
b-Nollet
7d32f12e4a re-handling redirections 2023-11-29 10:24:58 +01:00
b-Nollet
716e21dea8 returning full response 2023-11-27 17:51:36 +01:00
b-Nollet
453eb6c21a reverting unrelated change 2023-11-27 17:44:19 +01:00
b-Nollet
19bcfcfc1d forgotten params 2023-11-27 12:06:17 +01:00
b-Nollet
fea7f3fb17 formatting 2023-11-27 10:25:58 +01:00
b-Nollet
cc708ed741 undici getResource xen-api 2023-11-27 10:23:21 +01:00
b-Nollet
b1a7a9c972 moving client to index 2023-11-21 17:35:45 +01:00
b-Nollet
ea6a31296d xml-rpc undici 2023-11-21 14:31:26 +01:00
b-Nollet
6d62d04215 chore(xen-api): json-rpc undici 2023-11-21 11:16:04 +01:00
7 changed files with 169 additions and 95 deletions

View File

@@ -63,5 +63,5 @@ defer(async ($defer, rawArgs) => {
}, 1e3)
)
await pipeline(exportStream, progressStream, throttle(bps), createOutputStream(args[2]))
await pipeline(exportStream.body, progressStream, throttle(bps), createOutputStream(args[2]))
})(process.argv.slice(2)).catch(console.error.bind(console, 'error'))

View File

@@ -47,7 +47,7 @@ defer(async ($defer, rawArgs) => {
console.warn('Export task:', exportStream.headers['task-id'])
await pipeline(
exportStream,
exportStream.body,
createProgress({ time: 1e3 }, p => console.warn(formatProgress(p))),
createOutputStream(args[2])
)

View File

@@ -2,10 +2,10 @@ import assert from 'assert'
import dns from 'dns'
import kindOf from 'kindof'
import ms from 'ms'
import httpRequest from 'http-request-plus'
import map from 'lodash/map.js'
import noop from 'lodash/noop.js'
import ProxyAgent from 'proxy-agent'
import { Client } from 'undici'
import { coalesceCalls } from '@vates/coalesce-calls'
import { Collection } from 'xo-collection'
import { EventEmitter } from 'events'
@@ -396,36 +396,53 @@ export class Xapi extends EventEmitter {
let url = new URL('http://localhost')
url.protocol = this._url.protocol
url.pathname = pathname
url.search = new URLSearchParams(query)
await this._setHostAddressInUrl(url, host)
const response = await this._addSyncStackTrace(
pRetry(
async () =>
httpRequest(url, {
rejectUnauthorized: !this._allowUnauthorized,
async () => {
const client = new Client(url, {
connect: {
rejectUnauthorized: !this._allowUnauthorized,
// Support XS <= 6.5 with Node => 12
minVersion: 'TLSv1',
},
})
// this is an inactivity timeout (unclear in Node doc)
timeout: this._httpInactivityTimeout,
maxRedirects: 0,
// Support XS <= 6.5 with Node => 12
minVersion: 'TLSv1',
agent: this.httpAgent,
signal: $cancelToken,
}),
return client
.request({
method: 'GET',
path: pathname,
query,
maxRedirections: 0,
headersTimeout: this._httpInactivityTimeout,
bodyTimeout: this._httpInactivityTimeout,
agent: this.httpAgent,
signal: $cancelToken,
})
.then(response => {
const { statusCode } = response
if (((statusCode / 100) | 0) === 2) {
return response
}
const error = new Error(response.statusCode)
Object.defineProperty(error, 'response', { value: response })
throw error
})
},
{
when: error => error.response !== undefined && error.response.statusCode === 302,
onRetry: async error => {
const response = error.response
if (response === undefined) {
if (response === undefined || response.body === undefined) {
throw error
}
response.destroy()
response.body.on('error', noop)
response.body.destroy()
url = await this._replaceHostAddressInUrl(new URL(response.headers.location, url))
query = Object.fromEntries(url.searchParams.entries())
pathname = url.pathname
url.pathname = url.search = ''
},
}
)
@@ -479,25 +496,49 @@ export class Xapi extends EventEmitter {
url.search = new URLSearchParams(query)
await this._setHostAddressInUrl(url, host)
const doRequest = (url, opts) =>
httpRequest(url, {
agent: this.httpAgent,
const doRequest = (url, opts) => {
query = Object.fromEntries(url.searchParams.entries())
pathname = url.pathname
url.pathname = url.search = ''
body,
headers,
method: 'PUT',
rejectUnauthorized: !this._allowUnauthorized,
signal: $cancelToken,
console.log("====> doing request <=====")
console.log("URL :", url)
console.log("query :", query)
console.log("path :", pathname)
// this is an inactivity timeout (unclear in Node doc)
timeout: this._httpInactivityTimeout,
// Support XS <= 6.5 with Node => 12
minVersion: 'TLSv1',
...opts,
const client = new Client(url, {
connect: {
rejectUnauthorized: !this._allowUnauthorized,
// Support XS <= 6.5 with Node => 12
minVersion: 'TLSv1',
...opts,
},
})
return client
.request({
agent: this.httpAgent,
body,
headers,
method: 'PUT',
path: pathname,
query,
signal: $cancelToken,
headersTimeout: this._httpInactivityTimeout,
bodyTimeout: this._httpInactivityTimeout,
...opts,
})
.then(response => {
const { statusCode } = response
if (((statusCode / 100) | 0) === 2) {
return response
}
const error = new Error(response.statusCode)
Object.defineProperty(error, 'response', { value: response })
throw error
})
}
const dummyUrl = new URL(url)
dummyUrl.searchParams.delete('task_id')
@@ -507,17 +548,19 @@ export class Xapi extends EventEmitter {
isStream
? doRequest(dummyUrl, {
body: '',
maxRedirects: 0,
}).then(
response => {
response.destroy()
response.body.on('error', noop)
response.body.destroy()
return doRequest(url)
},
async error => {
console.log("ERREUR")
let response
if (error != null && (response = error.response) != null) {
response.destroy()
response.body.on('error', noop)
response.body.destroy()
const {
headers: { location },
@@ -537,6 +580,9 @@ export class Xapi extends EventEmitter {
: doRequest(url)
)
console.log("statusCode :", response.statusCode)
if (pTaskResult !== undefined) {
if (useHack) {
// In case of the hack, ignore (but log) the very probably `VDI_IO_ERROR` because it is usually irrelevant
@@ -545,7 +591,7 @@ export class Xapi extends EventEmitter {
})
} else {
pTaskResult = pTaskResult.catch(error => {
error.url = response.url
error.url = response.body.url
throw error
})
@@ -555,24 +601,25 @@ export class Xapi extends EventEmitter {
}
try {
const { req } = response
if (!req.finished) {
if (!response.body.bodyUsed) {
await new Promise((resolve, reject) => {
req.on('finish', resolve)
response.body.on('finish', resolve)
response.on('error', reject)
})
}
if (useHack) {
response.destroy()
response.body.on('error', noop)
response.body.destroy()
} else {
// consume the response
response.resume()
response.body.resume()
await new Promise((resolve, reject) => {
response.on('end', resolve).on('error', reject)
response.body.on('end', resolve).on('error', reject)
})
}
} catch (error) {
console.log("passé par là")
if (this._ignorePrematureClose && error.code === 'ERR_STREAM_PREMATURE_CLOSE') {
console.warn(this._humanId, 'Xapi#putResource', pathname, error)
} else {
@@ -946,14 +993,18 @@ export class Xapi extends EventEmitter {
const { hostname } = url
url.hostnameRaw = hostname[0] === '[' ? hostname.slice(1, -1) : hostname
this._humanId = `${this._auth.user ?? 'unknown'}@${url.hostname}`
this._transport = this._createTransport({
secureOptions: {
const client = new Client(url, {
connect: {
minVersion: 'TLSv1',
rejectUnauthorized: !this._allowUnauthorized,
},
url,
})
this._humanId = `${this._auth.user ?? 'unknown'}@${url.hostname}`
this._transport = this._createTransport({
agent: this.httpAgent,
client,
url,
})
this._url = url
}

View File

@@ -48,7 +48,8 @@
"promise-toolbox": "^0.21.0",
"proxy-agent": "^5.0.0",
"pw": "0.0.4",
"xmlrpc": "^1.3.2",
"undici": "^5.27.2",
"xmlrpc-parser": "^1.0.3",
"xo-collection": "^0.5.0"
},
"devDependencies": {

View File

@@ -1,4 +1,3 @@
import httpRequestPlus from 'http-request-plus'
import { format, parse } from 'json-rpc-protocol'
import XapiError from '../_XapiError.mjs'
@@ -6,18 +5,19 @@ import XapiError from '../_XapiError.mjs'
import UnsupportedTransport from './_UnsupportedTransport.mjs'
// https://github.com/xenserver/xenadmin/blob/0df39a9d83cd82713f32d24704852a0fd57b8a64/XenModel/XenAPI/Session.cs#L403-L433
export default ({ secureOptions, url, agent }) => {
export default ({ agent, client, url }) => {
url = new URL('./jsonrpc', Object.assign(new URL('http://localhost'), url))
const path = url.pathname + url.search
return async function (method, args) {
const res = await httpRequestPlus(url, {
...secureOptions,
const res = await client.request({
body: format.request(0, method, args),
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
},
method: 'POST',
path,
agent,
})
@@ -26,7 +26,7 @@ export default ({ secureOptions, url, agent }) => {
throw new UnsupportedTransport()
}
const response = parse(await res.text())
const response = parse(await res.body.text())
if (response.type === 'response') {
return response.result

View File

@@ -1,18 +1,8 @@
import xmlrpc from 'xmlrpc'
import { promisify } from 'promise-toolbox'
import XapiError from '../_XapiError.mjs'
import { XmlRpcMessage, XmlRpcResponse } from 'xmlrpc-parser'
import prepareXmlRpcParams from './_prepareXmlRpcParams.mjs'
const logError = error => {
if (error.res) {
console.error('XML-RPC Error: %s (response status %s)', error.message, error.res.statusCode)
console.error('%s', error.body)
}
throw error
}
import XapiError from '../_XapiError.mjs'
import UnsupportedTransport from './_UnsupportedTransport.mjs'
const parseResult = result => {
const status = result.Status
@@ -30,16 +20,31 @@ const parseResult = result => {
return result.Value
}
export default ({ secureOptions, url: { hostnameRaw, pathname, port, protocol }, agent }) => {
const secure = protocol === 'https:'
const client = (secure ? xmlrpc.createSecureClient : xmlrpc.createClient)({
...(secure ? secureOptions : undefined),
agent,
host: hostnameRaw,
pathname,
port,
})
const call = promisify(client.methodCall, client)
export default ({ agent, client, url }) => {
url = new URL('./xmlrpc', Object.assign(new URL('http://localhost'), url))
const path = url.pathname + url.search
return (method, args) => call(method, prepareXmlRpcParams(args)).then(parseResult, logError)
return async function (method, args) {
const message = new XmlRpcMessage(method, prepareXmlRpcParams(args))
const res = await client.request({
body: message.xml(),
headers: {
Accept: 'text/xml',
'Content-Type': 'text/xml',
},
method: 'POST',
path,
agent,
})
if (res.headers['content-type'] !== 'text/xml' && res.headers['content-type'] !== 'application/xml') {
throw new UnsupportedTransport()
}
const xml = await res.body.text()
const response = await new XmlRpcResponse().parse(xml)
return parseResult(response.params[0])
}
}

View File

@@ -2106,6 +2106,11 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.54.0.tgz#4fab9a2ff7860082c304f750e94acd644cf984cf"
integrity sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==
"@fastify/busboy@^2.0.0":
version "2.1.0"
resolved "https://registry.yarnpkg.com/@fastify/busboy/-/busboy-2.1.0.tgz#0709e9f4cb252351c609c6e6d8d6779a8d25edff"
integrity sha512-+KpH+QxZU7O4675t3mnkQKcZZg56u+K/Ct2K+N2AZYNVK8kyeo/bI18tI8aPm3tvNNRyTWfj6s5tnGNlcbQRsA==
"@fontsource/poppins@^5.0.8":
version "5.0.8"
resolved "https://registry.yarnpkg.com/@fontsource/poppins/-/poppins-5.0.8.tgz#a1c5540aedb3719a36eba5c7c5dfaa3aed3c9f80"
@@ -18782,16 +18787,21 @@ sass@^1.38.1:
immutable "^4.0.0"
source-map-js ">=0.6.2 <2.0.0"
sax@1.2.x, sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
sax-parser@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/sax-parser/-/sax-parser-2.0.2.tgz#7b3b4a25fc69bf4e729ad5f0f98430205d461689"
integrity sha512-EjLxlFjZdmv/cpOwV+klYEeOYjR2Dc9C495d2Ruk+N6xknrOnIfjSum2a63hfi9Vox2fCsjYc3NuDVo0YkGpjg==
sax@>=0.6, sax@>=0.6.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0"
integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==
sax@~1.2.4:
version "1.2.4"
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
scheduler@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
@@ -20809,6 +20819,13 @@ undici-types@~5.26.4:
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617"
integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==
undici@^5.27.2:
version "5.27.2"
resolved "https://registry.yarnpkg.com/undici/-/undici-5.27.2.tgz#a270c563aea5b46cc0df2550523638c95c5d4411"
integrity sha512-iS857PdOEy/y3wlM3yRp+6SNQQ6xU0mmZcwRSriqk+et/cwWAtwmIGf6WkoDN2EK/AMdCO/dfXzIwi+rFMrjjQ==
dependencies:
"@fastify/busboy" "^2.0.0"
unicode-canonical-property-names-ecmascript@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc"
@@ -21033,6 +21050,11 @@ use@^3.1.0:
resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f"
integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==
utf8-base64@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/utf8-base64/-/utf8-base64-0.1.2.tgz#555806c458f9ba3f089c3ebe0c5f6198348bb57b"
integrity sha512-DNeEx/I7HruiVsfk/DbEl4bpdRR/mv5p6FGDFZVyA8wqdMOqYp0CeCgW4/DzsPIW/skOq5Bxv49/eYfvAYJTWg==
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
@@ -21981,11 +22003,6 @@ xml2js@^0.4.19, xml2js@^0.4.23:
sax ">=0.6.0"
xmlbuilder "~11.0.0"
xmlbuilder@8.2.x:
version "8.2.2"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-8.2.2.tgz#69248673410b4ba42e1a6136551d2922335aa773"
integrity sha512-eKRAFz04jghooy8muekqzo8uCSVNeyRedbuJrp0fovbLIi7wlsYtdUn3vBAAPq2Y3/0xMz2WMEUQ8yhVVO9Stw==
xmlbuilder@^15.1.1:
version "15.1.1"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-15.1.1.tgz#9dcdce49eea66d8d10b42cae94a79c3c8d0c2ec5"
@@ -21996,13 +22013,13 @@ xmlbuilder@~11.0.0:
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3"
integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==
xmlrpc@^1.3.2:
version "1.3.2"
resolved "https://registry.yarnpkg.com/xmlrpc/-/xmlrpc-1.3.2.tgz#26b2ea347848d028aac7e7514b5351976de3e83d"
integrity sha512-jQf5gbrP6wvzN71fgkcPPkF4bF/Wyovd7Xdff8d6/ihxYmgETQYSuTc+Hl+tsh/jmgPLro/Aro48LMFlIyEKKQ==
xmlrpc-parser@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/xmlrpc-parser/-/xmlrpc-parser-1.0.3.tgz#94f21bb74daaa2290a51471635c73f8b6dc1f3d9"
integrity sha512-0197DF6MrKFoiaccl2GuB5mcc3F0jSebPLHIqsahpau4yyztg34bVZDhc6HGzs5hji801prnytCKTo4Kdpa7Rw==
dependencies:
sax "1.2.x"
xmlbuilder "8.2.x"
sax-parser "^2.0.2"
utf8-base64 "^0.1.2"
xok@^1.0.0:
version "1.0.0"