-
Notifications
You must be signed in to change notification settings - Fork 8.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
* retry SO import on conflict errors * add jitter + increase retries * Apply suggestions from code review Co-authored-by: Josh Dover <[email protected]> Co-authored-by: Josh Dover <[email protected]> (cherry picked from commit 7c6d314) Co-authored-by: Mark Hopkin <[email protected]>
- Loading branch information
1 parent
027ca22
commit 50c7904
Showing
2 changed files
with
167 additions
and
8 deletions.
There are no files selected for viewing
124 changes: 124 additions & 0 deletions
124
x-pack/plugins/fleet/server/services/epm/kibana/assets/install.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/* | ||
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one | ||
* or more contributor license agreements. Licensed under the Elastic License | ||
* 2.0; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
import type { | ||
ISavedObjectsImporter, | ||
SavedObjectsImportFailure, | ||
SavedObjectsImportSuccess, | ||
SavedObjectsImportResponse, | ||
} from 'src/core/server'; | ||
|
||
import { loggingSystemMock } from '../../../../../../../../src/core/server/mocks'; | ||
|
||
import type { ArchiveAsset } from './install'; | ||
|
||
jest.mock('timers/promises', () => ({ | ||
async setTimeout() {}, | ||
})); | ||
|
||
import { installKibanaSavedObjects } from './install'; | ||
|
||
const mockLogger = loggingSystemMock.createLogger(); | ||
|
||
const mockImporter: jest.Mocked<ISavedObjectsImporter> = { | ||
import: jest.fn(), | ||
resolveImportErrors: jest.fn(), | ||
}; | ||
|
||
const createImportError = (so: ArchiveAsset, type: string) => | ||
({ id: so.id, error: { type } } as SavedObjectsImportFailure); | ||
const createImportSuccess = (so: ArchiveAsset) => | ||
({ id: so.id, type: so.type, meta: {} } as SavedObjectsImportSuccess); | ||
const createAsset = (asset: Partial<ArchiveAsset>) => | ||
({ id: 1234, type: 'dashboard', attributes: {}, ...asset } as ArchiveAsset); | ||
|
||
const createImportResponse = ( | ||
errors: SavedObjectsImportFailure[] = [], | ||
successResults: SavedObjectsImportSuccess[] = [] | ||
) => | ||
({ | ||
success: !!successResults.length, | ||
errors, | ||
successResults, | ||
warnings: [], | ||
successCount: successResults.length, | ||
} as SavedObjectsImportResponse); | ||
|
||
describe('installKibanaSavedObjects', () => { | ||
beforeEach(() => { | ||
mockImporter.import.mockReset(); | ||
mockImporter.resolveImportErrors.mockReset(); | ||
}); | ||
|
||
it('should retry on conflict error', async () => { | ||
const asset = createAsset({ attributes: { hello: 'world' } }); | ||
const conflictResponse = createImportResponse([createImportError(asset, 'conflict')]); | ||
const successResponse = createImportResponse([], [createImportSuccess(asset)]); | ||
|
||
mockImporter.import | ||
.mockResolvedValueOnce(conflictResponse) | ||
.mockResolvedValueOnce(successResponse); | ||
|
||
await installKibanaSavedObjects({ | ||
savedObjectsImporter: mockImporter, | ||
logger: mockLogger, | ||
kibanaAssets: [asset], | ||
}); | ||
|
||
expect(mockImporter.import).toHaveBeenCalledTimes(2); | ||
}); | ||
|
||
it('should give up after 50 retries on conflict errors', async () => { | ||
const asset = createAsset({ attributes: { hello: 'world' } }); | ||
const conflictResponse = createImportResponse([createImportError(asset, 'conflict')]); | ||
|
||
mockImporter.import.mockImplementation(() => Promise.resolve(conflictResponse)); | ||
|
||
await expect( | ||
installKibanaSavedObjects({ | ||
savedObjectsImporter: mockImporter, | ||
logger: mockLogger, | ||
kibanaAssets: [asset], | ||
}) | ||
).rejects.toEqual(expect.any(Error)); | ||
expect(mockImporter.import).toHaveBeenCalledTimes(51); | ||
}); | ||
it('should not retry errors that arent conflict errors', async () => { | ||
const asset = createAsset({ attributes: { hello: 'world' } }); | ||
const errorResponse = createImportResponse([createImportError(asset, 'something_bad')]); | ||
const successResponse = createImportResponse([], [createImportSuccess(asset)]); | ||
|
||
mockImporter.import.mockResolvedValueOnce(errorResponse).mockResolvedValueOnce(successResponse); | ||
|
||
expect( | ||
installKibanaSavedObjects({ | ||
savedObjectsImporter: mockImporter, | ||
logger: mockLogger, | ||
kibanaAssets: [asset], | ||
}) | ||
).rejects.toEqual(expect.any(Error)); | ||
}); | ||
|
||
it('should resolve reference errors', async () => { | ||
const asset = createAsset({ attributes: { hello: 'world' } }); | ||
const referenceErrorResponse = createImportResponse([ | ||
createImportError(asset, 'missing_references'), | ||
]); | ||
const successResponse = createImportResponse([], [createImportSuccess(asset)]); | ||
|
||
mockImporter.import.mockResolvedValueOnce(referenceErrorResponse); | ||
mockImporter.resolveImportErrors.mockResolvedValueOnce(successResponse); | ||
|
||
await installKibanaSavedObjects({ | ||
savedObjectsImporter: mockImporter, | ||
logger: mockLogger, | ||
kibanaAssets: [asset], | ||
}); | ||
|
||
expect(mockImporter.import).toHaveBeenCalledTimes(1); | ||
expect(mockImporter.resolveImportErrors).toHaveBeenCalledTimes(1); | ||
}); | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters