diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 23cf2af3e..353b5a9d7 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -32,6 +32,7 @@ > > In case of conflict, the highest (lowest in previous list) `$version` wins. +- xen-api minor - xo-server-backup-reports patch - xo-web minor - xo-server patch diff --git a/packages/xen-api/src/cli.js b/packages/xen-api/src/cli.js index 9d3e6b3b2..668900384 100755 --- a/packages/xen-api/src/cli.js +++ b/packages/xen-api/src/cli.js @@ -42,6 +42,7 @@ const usage = 'Usage: xen-api [ []]' async function main(createClient) { const opts = minimist(process.argv.slice(2), { + string: ['session-id'], boolean: ['allow-unauthorized', 'help', 'read-only', 'verbose'], alias: { @@ -68,6 +69,8 @@ async function main(createClient) { if (opts._.length > 1) { const [, user, password = await askPassword()] = opts._ auth = { user, password } + } else if (opts['session-id'] !== undefined) { + auth = { sessionId: opts['session-id'] } } { diff --git a/packages/xen-api/src/index.js b/packages/xen-api/src/index.js index 3c097901e..15e3bfb94 100644 --- a/packages/xen-api/src/index.js +++ b/packages/xen-api/src/index.js @@ -700,25 +700,34 @@ export class Xapi extends EventEmitter { _sessionCallRetryOptions = { tries: 2, - when: error => this._status !== DISCONNECTED && error?.code === 'SESSION_INVALID', + when: error => + this._status !== DISCONNECTED && error?.code === 'SESSION_INVALID' && this._auth.password !== undefined, onRetry: () => this._sessionOpen(), } - _sessionCall(method, args, timeout) { + async _sessionCall(method, args, timeout) { if (method.startsWith('session.')) { return Promise.reject(new Error('session.*() methods are disabled from this interface')) } - return pRetry(() => { - const sessionId = this._sessionId - assert.notStrictEqual(sessionId, undefined) + try { + return await pRetry(() => { + const sessionId = this._sessionId + assert.notStrictEqual(sessionId, undefined) - const newArgs = [sessionId] - if (args !== undefined) { - newArgs.push.apply(newArgs, args) + const newArgs = [sessionId] + if (args !== undefined) { + newArgs.push.apply(newArgs, args) + } + + return this._call(method, newArgs, timeout) + }, this._sessionCallRetryOptions) + } catch (error) { + if (error?.code === 'SESSION_INVALID') { + await ignoreErrors.call(this.disconnect()) } - return this._call(method, newArgs, timeout) - }, this._sessionCallRetryOptions) + throw error + } } // FIXME: (probably rare) race condition leading to unnecessary login when: @@ -728,29 +737,40 @@ export class Xapi extends EventEmitter { // 3. the session is renewed // 4. the second call fails with SESSION_INVALID which leads to a new // unnecessary renewal + _sessionOpenRetryOptions = { + tries: 2, + when: { code: 'HOST_IS_SLAVE' }, + onRetry: error => { + this._setUrl({ ...this._url, hostname: error.params[0] }) + }, + } _sessionOpen = coalesceCalls(this._sessionOpen) async _sessionOpen() { - const { user, password } = this._auth - const params = [user, password] - this._sessionId = await pRetry( - () => this._interruptOnDisconnect(this._call('session.login_with_password', params)), - { - tries: 2, - when: { code: 'HOST_IS_SLAVE' }, - onRetry: error => { - this._setUrl({ ...this._url, hostname: error.params[0] }) - }, - } - ) + const { user, password, sessionId } = this._auth + + this._sessionId = sessionId + + if (sessionId === undefined) { + const params = [user, password] + this._sessionId = await pRetry( + () => this._interruptOnDisconnect(this._call('session.login_with_password', params)), + this._sessionOpenRetryOptions + ) + } const oldPoolRef = this._pool?.$ref // Similar to `(await this.getAllRecords('pool'))[0]` but prevents a // deadlock in case of error due to a pRetry calling _sessionOpen again - const pools = await this._call('pool.get_all_records', [this._sessionId]) + const pools = await pRetry( + () => this._call('pool.get_all_records', [this._sessionId]), + this._sessionOpenRetryOptions + ) const poolRef = Object.keys(pools)[0] this._pool = this._wrapRecord('pool', poolRef, pools[poolRef]) + this.emit('sessionId', this._sessionId) + // if the pool ref has changed, it means that the XAPI has been restarted or // it's not the same XAPI, we need to refetch the available types and reset // the event loop in that case @@ -781,7 +801,7 @@ export class Xapi extends EventEmitter { } _setUrl(url) { - this._humanId = `${this._auth.user}@${url.hostname}` + this._humanId = `${this._auth.user ?? 'unknown'}@${url.hostname}` this._transport = autoTransport({ secureOptions: { minVersion: 'TLSv1',