feat(nbd-client,xapi): implement multiple connections NBD

This commit is contained in:
Florent Beauchamp
2023-12-19 15:28:32 +01:00
committed by Julien Fontanet
parent 7ddfb2a684
commit b644cbe28d
3 changed files with 75 additions and 8 deletions
+64
View File
@@ -0,0 +1,64 @@
import { asyncEach } from '@vates/async-each'
import { NBD_DEFAULT_BLOCK_SIZE } from './constants.mjs'
import NbdClient from './index.mjs'
export default class MultiNbdClient {
#clients = []
#readAhead
constructor(settings, { nbdConcurrency = 8, readAhead = 16, ...options } = {}) {
this.#readAhead = readAhead
if (!Array.isArray(settings)) {
settings = [settings]
}
for (let i = 0; i < nbdConcurrency; i++) {
this.#clients.push(
new NbdClient(settings[i % settings.length], { ...options, readAhead: Math.ceil(readAhead / nbdConcurrency) })
)
}
}
async connect() {
for (const client of this.#clients) {
await client.connect()
}
}
async disconnect() {
asyncEach(this.#clients,
client => client.disconnect(),
{
stopOnError: false
})
}
async readBlock(index, size = NBD_DEFAULT_BLOCK_SIZE) {
const clientId = index % this.#clients.length
return this.#clients[clientId].readBlock(index, size)
}
async *readBlocks(indexGenerator) {
// default : read all blocks
const readAhead = []
const makeReadBlockPromise = (index, size) => {
const promise = this.readBlock(index, size)
// error is handled during unshift
promise.catch(() => {})
return promise
}
// read all blocks, but try to keep readAheadMaxLength promise waiting ahead
for (const { index, size } of indexGenerator()) {
// stack readAheadMaxLength promises before starting to handle the results
if (readAhead.length === this.#readAhead) {
// any error will stop reading blocks
yield readAhead.shift()
}
readAhead.push(makeReadBlockPromise(index, size))
}
while (readAhead.length > 0) {
yield readAhead.shift()
}
}
}
+9 -7
View File
@@ -6,14 +6,13 @@ import { decorateClass } from '@vates/decorate-with'
import { strict as assert } from 'node:assert'
import extractOpaqueRef from './_extractOpaqueRef.mjs'
import NbdClient from '@vates/nbd-client'
import { createNbdRawStream, createNbdVhdStream } from 'vhd-lib/createStreamNbd.js'
import MultiNbdClient from '@vates/nbd-client/multi.mjs'
import { createNbdVhdStream, createNbdRawStream } from 'vhd-lib/createStreamNbd.js'
import { VDI_FORMAT_RAW, VDI_FORMAT_VHD } from './index.mjs'
const { warn } = createLogger('xo:xapi:vdi')
const noop = Function.prototype
class Vdi {
async clone(vdiRef) {
return extractOpaqueRef(await this.callAsync('VDI.clone', vdiRef))
@@ -64,13 +63,13 @@ class Vdi {
})
}
async _getNbdClient(ref) {
async _getNbdClient(ref, { nbdConcurrency = 1 } = {}) {
const nbdInfos = await this.call('VDI.get_nbd_info', ref)
if (nbdInfos.length > 0) {
// a little bit of randomization to spread the load
const nbdInfo = nbdInfos[Math.floor(Math.random() * nbdInfos.length)]
try {
const nbdClient = new NbdClient(nbdInfo, this._nbdOptions)
const nbdClient = new MultiNbdClient(nbdInfos, { ...this._nbdOptions, nbdConcurrency })
await nbdClient.connect()
return nbdClient
} catch (err) {
@@ -83,7 +82,10 @@ class Vdi {
}
}
async exportContent(ref, { baseRef, cancelToken = CancelToken.none, format, preferNbd = this._preferNbd }) {
async exportContent(
ref,
{ baseRef, cancelToken = CancelToken.none, format, nbdConcurrency = 1, preferNbd = this._preferNbd }
) {
const query = {
format,
vdi: ref,
@@ -97,7 +99,7 @@ class Vdi {
let nbdClient, stream
try {
if (preferNbd) {
nbdClient = await this._getNbdClient(ref)
nbdClient = await this._getNbdClient(ref, { nbdConcurrency })
}
// the raw nbd export does not need to peek ath the vhd source
if (nbdClient !== undefined && format === VDI_FORMAT_RAW) {
+2 -1
View File
@@ -42,7 +42,8 @@
<!--packages-start-->
- @xen-orchestra/xapi patch
- @vates/nbd-client minor
- @xen-orchestra/xapi minor
- vhd-lib patch
- xo-server minor
- xo-server-auth-saml minor