feat(xo-server/moveVdi): retry on TOO_MANY_STORAGE_MIGRATES (#3940)
Fix xoa-support#1222
This commit is contained in:
parent
b2813d7cc0
commit
e484b073e1
@ -16,6 +16,8 @@ class TestHandler extends AbstractHandler {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jest.useFakeTimers()
|
||||||
|
|
||||||
describe('closeFile()', () => {
|
describe('closeFile()', () => {
|
||||||
it(`throws in case of timeout`, async () => {
|
it(`throws in case of timeout`, async () => {
|
||||||
const testHandler = new TestHandler({
|
const testHandler = new TestHandler({
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
- [Home] Set description on bulk snapshot [#3925](https://github.com/vatesfr/xen-orchestra/issues/3925) (PR [#3933](https://github.com/vatesfr/xen-orchestra/pull/3933))
|
- [Home] Set description on bulk snapshot [#3925](https://github.com/vatesfr/xen-orchestra/issues/3925) (PR [#3933](https://github.com/vatesfr/xen-orchestra/pull/3933))
|
||||||
- Work-around the XenServer issue when `VBD#VDI` is an empty string instead of an opaque reference (PR [#3950](https://github.com/vatesfr/xen-orchestra/pull/3950))
|
- Work-around the XenServer issue when `VBD#VDI` is an empty string instead of an opaque reference (PR [#3950](https://github.com/vatesfr/xen-orchestra/pull/3950))
|
||||||
- [VM Snapshotting] Detect and destroy broken quiesced snapshot left by XenServer [#3936](https://github.com/vatesfr/xen-orchestra/issues/3936) (PR [#3937](https://github.com/vatesfr/xen-orchestra/pull/3937))
|
- [VM Snapshotting] Detect and destroy broken quiesced snapshot left by XenServer [#3936](https://github.com/vatesfr/xen-orchestra/issues/3936) (PR [#3937](https://github.com/vatesfr/xen-orchestra/pull/3937))
|
||||||
|
- [VDI migration] Retry when XenServer fails with `TOO_MANY_STORAGE_MIGRATES` (PR [#3940](https://github.com/vatesfr/xen-orchestra/pull/3940))
|
||||||
|
|
||||||
### Bug fixes
|
### Bug fixes
|
||||||
|
|
||||||
|
@ -34,7 +34,6 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"jest": {
|
"jest": {
|
||||||
"timers": "fake",
|
|
||||||
"collectCoverage": true,
|
"collectCoverage": true,
|
||||||
"projects": [
|
"projects": [
|
||||||
"<rootDir>"
|
"<rootDir>"
|
||||||
|
36
packages/xo-server/src/_pRetry.js
Normal file
36
packages/xo-server/src/_pRetry.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import iteratee from 'lodash/iteratee'
|
||||||
|
import pDelay from 'promise-toolbox/delay'
|
||||||
|
|
||||||
|
function stopRetry(error) {
|
||||||
|
this.error = error
|
||||||
|
// eslint-disable-next-line no-throw-literal
|
||||||
|
throw this
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not retry on ReferenceError and TypeError which are programmer errors
|
||||||
|
const defaultMatcher = error =>
|
||||||
|
!(error instanceof ReferenceError || error instanceof TypeError)
|
||||||
|
|
||||||
|
export default async function pRetry(
|
||||||
|
fn,
|
||||||
|
{ delay = 1e3, tries = 10, when } = {}
|
||||||
|
) {
|
||||||
|
const container = { error: undefined }
|
||||||
|
const stop = stopRetry.bind(container)
|
||||||
|
|
||||||
|
when = when === undefined ? defaultMatcher : iteratee(when)
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
try {
|
||||||
|
return await fn(stop)
|
||||||
|
} catch (error) {
|
||||||
|
if (error === container) {
|
||||||
|
throw container.error
|
||||||
|
}
|
||||||
|
if (--tries === 0 || !when(error)) {
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await pDelay(delay)
|
||||||
|
}
|
||||||
|
}
|
46
packages/xo-server/src/_pRetry.spec.js
Normal file
46
packages/xo-server/src/_pRetry.spec.js
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
/* eslint-env jest */
|
||||||
|
|
||||||
|
import pRetry from './_pRetry'
|
||||||
|
|
||||||
|
describe('pRetry()', () => {
|
||||||
|
it('retries until the function succeeds', async () => {
|
||||||
|
let i = 0
|
||||||
|
expect(
|
||||||
|
await pRetry(
|
||||||
|
() => {
|
||||||
|
if (++i < 3) {
|
||||||
|
throw new Error()
|
||||||
|
}
|
||||||
|
return 'foo'
|
||||||
|
},
|
||||||
|
{ delay: 0 }
|
||||||
|
)
|
||||||
|
).toBe('foo')
|
||||||
|
expect(i).toBe(3)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns the last error', async () => {
|
||||||
|
let tries = 5
|
||||||
|
const e = new Error()
|
||||||
|
await expect(
|
||||||
|
pRetry(
|
||||||
|
() => {
|
||||||
|
throw --tries > 0 ? new Error() : e
|
||||||
|
},
|
||||||
|
{ delay: 0, tries }
|
||||||
|
)
|
||||||
|
).rejects.toBe(e)
|
||||||
|
})
|
||||||
|
;[ReferenceError, TypeError].forEach(ErrorType => {
|
||||||
|
it(`does not retry if a ${ErrorType.name} is thrown`, async () => {
|
||||||
|
let i = 0
|
||||||
|
await expect(
|
||||||
|
pRetry(() => {
|
||||||
|
++i
|
||||||
|
throw new ErrorType()
|
||||||
|
})
|
||||||
|
).rejects.toBeInstanceOf(ErrorType)
|
||||||
|
expect(i).toBe(1)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -36,6 +36,7 @@ import { satisfies as versionSatisfies } from 'semver'
|
|||||||
|
|
||||||
import createSizeStream from '../size-stream'
|
import createSizeStream from '../size-stream'
|
||||||
import fatfsBuffer, { init as fatfsBufferInit } from '../fatfs-buffer'
|
import fatfsBuffer, { init as fatfsBufferInit } from '../fatfs-buffer'
|
||||||
|
import pRetry from '../_pRetry'
|
||||||
import {
|
import {
|
||||||
camelToSnakeCase,
|
camelToSnakeCase,
|
||||||
ensureArray,
|
ensureArray,
|
||||||
@ -1869,7 +1870,9 @@ export default class Xapi extends XapiBase {
|
|||||||
}`
|
}`
|
||||||
)
|
)
|
||||||
try {
|
try {
|
||||||
await this.call('VDI.pool_migrate', vdi.$ref, sr.$ref, {})
|
await pRetry(() => this.call('VDI.pool_migrate', vdi.$ref, sr.$ref, {}), {
|
||||||
|
when: { code: 'TOO_MANY_STORAGE_MIGRATES' },
|
||||||
|
})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const { code } = error
|
const { code } = error
|
||||||
if (
|
if (
|
||||||
|
Loading…
Reference in New Issue
Block a user