diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 22dd8c209..2bd907121 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -13,6 +13,7 @@ - [Pool] Fix `an error has occurred` when using the "Disconnect" button from the pool page [#5669](https://github.com/vatesfr/xen-orchestra/issues/5669) (PR [#5671](https://github.com/vatesfr/xen-orchestra/pull/5671)) - [Configuration] Automatically connect enabled servers after import [#5660](https://github.com/vatesfr/xen-orchestra/issues/5660) (PR [#5672](https://github.com/vatesfr/xen-orchestra/pull/5672)) +- Work-around some `ECONNRESET` errors when connecting to XEN-API (PR [#5674](https://github.com/vatesfr/xen-orchestra/pull/5674)) ### Packages to release @@ -31,5 +32,6 @@ > > In case of conflict, the highest (lowest in previous list) `$version` wins. +- xen-api minor - xo-server patch - xo-web patch diff --git a/packages/xen-api/src/index.js b/packages/xen-api/src/index.js index dca204a93..3b63165fa 100644 --- a/packages/xen-api/src/index.js +++ b/packages/xen-api/src/index.js @@ -86,6 +86,13 @@ export class Xapi extends EventEmitter { this._readOnly = Boolean(opts.readOnly) this._RecordsByType = { __proto__: null } + this._roCallRetryOptions = { + delay: 1e3, + tries: 10, + ...opts.roCallRetryOptions, + when: { code: 'ECONNRESET' }, + } + this._auth = opts.auth const url = parseUrl(opts.url) if (this._auth === undefined) { @@ -231,7 +238,9 @@ export class Xapi extends EventEmitter { // this should be used for instantaneous calls, otherwise use `callAsync` call(method, ...args) { - return this._readOnly && !isReadOnlyCall(method, args) + return isReadOnlyCall(method, args) + ? this._roCall(method, args) + : this._readOnly ? Promise.reject(new Error(`cannot call ${method}() in read only mode`)) : this._sessionCall(method, args) } @@ -261,15 +270,15 @@ export class Xapi extends EventEmitter { // =========================================================================== async getAllRecords(type) { - return map(await this._sessionCall(`${type}.get_all_records`), (record, ref) => this._wrapRecord(type, ref, record)) + return map(await this._roCall(`${type}.get_all_records`), (record, ref) => this._wrapRecord(type, ref, record)) } async getRecord(type, ref) { - return this._wrapRecord(type, ref, await this._sessionCall(`${type}.get_record`, [ref])) + return this._wrapRecord(type, ref, await this._roCall(`${type}.get_record`, [ref])) } async getRecordByUuid(type, uuid) { - return this.getRecord(type, await this._sessionCall(`${type}.get_by_uuid`, [uuid])) + return this.getRecord(type, await this._roCall(`${type}.get_by_uuid`, [uuid])) } getRecords(type, refs) { @@ -277,7 +286,7 @@ export class Xapi extends EventEmitter { } getField(type, ref, field) { - return this._sessionCall(`${type}.get_${field}`, [ref]) + return this._roCall(`${type}.get_${field}`, [ref]) } setField(type, ref, field, value) { @@ -849,7 +858,7 @@ export class Xapi extends EventEmitter { types.map(async type => { try { const toRemove = toRemoveByType[type] - const records = await this._sessionCall(`${type}.get_all_records`) + const records = await this._roCall(`${type}.get_all_records`) const refs = getKeys(records) refs.forEach(ref => { toRemove.delete(ref) @@ -900,6 +909,11 @@ export class Xapi extends EventEmitter { } } + // read-only call, automatically retry in case of connection issues + _roCall(method, args) { + return pRetry(() => this._sessionCall(method, args), this._roCallRetryOptions) + } + _watchEvents = coalesceCalls(this._watchEvents) async _watchEvents() { // eslint-disable-next-line no-labels @@ -1015,7 +1029,7 @@ export class Xapi extends EventEmitter { try { await this._connected - this._processEvents(await this._sessionCall('event.next', undefined, EVENT_TIMEOUT * 1e3)) + this._processEvents(await this._roCall('event.next', undefined, EVENT_TIMEOUT * 1e3)) } catch (error) { if (error?.code === 'EVENTS_LOST') { await ignoreErrors.call(this._sessionCall('event.unregister', [types]))