Skip to content

Commit

Permalink
Retry the execution of the connector
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasikas committed Dec 21, 2023
1 parent aac09db commit 3b9c564
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 0 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,13 @@ import { CasesOracleService } from './cases_oracle_service';
import { CasesService } from './cases_service';
import { CasesConnectorError } from './cases_connector_error';
import { CaseError } from '../../common/error';
import { fullJitterBackoffFactory } from './full_jitter_backoff';

jest.mock('./cases_connector_executor');
jest.mock('./full_jitter_backoff');

const CasesConnectorExecutorMock = CasesConnectorExecutor as jest.Mock;
const fullJitterBackoffFactoryMock = fullJitterBackoffFactory as jest.Mock;

describe('CasesConnector', () => {
const services = actionsMock.createServices();
Expand All @@ -37,18 +40,27 @@ describe('CasesConnector', () => {

const mockExecute = jest.fn();
const getCasesClient = jest.fn().mockResolvedValue({ foo: 'bar' });
// 1ms delay before retrying
const nextBackOff = jest.fn().mockReturnValue(1);

const backOffFactory = {
create: () => ({ nextBackOff }),
};

let connector: CasesConnector;

beforeEach(() => {
jest.clearAllMocks();
mockExecute.mockResolvedValue({});

CasesConnectorExecutorMock.mockImplementation(() => {
return {
execute: mockExecute,
};
});

fullJitterBackoffFactoryMock.mockReturnValue(backOffFactory);

connector = new CasesConnector({
casesParams: { getCasesClient },
connectorParams: {
Expand Down Expand Up @@ -156,4 +168,23 @@ describe('CasesConnector', () => {
})
).rejects.toThrowErrorMatchingInlineSnapshot(`"Server error"`);
});

it('retries correctly', async () => {
mockExecute
.mockRejectedValueOnce(new CasesConnectorError('Conflict error', 409))
.mockRejectedValueOnce(new CasesConnectorError('ES Unavailable', 503))
.mockResolvedValue({});

await connector.run({
alerts: [],
groupingBy,
owner,
rule,
timeWindow,
reopenClosedCases,
});

expect(nextBackOff).toBeCalledTimes(2);
expect(mockExecute).toBeCalledTimes(3);
});
});
14 changes: 14 additions & 0 deletions x-pack/plugins/cases/server/connectors/cases/cases_connector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ import {
isCasesConnectorError,
} from './cases_connector_error';
import { CasesConnectorExecutor } from './cases_connector_executor';
import { CaseConnectorRetryService } from './retry_service';
import { fullJitterBackoffFactory } from './full_jitter_backoff';

interface CasesConnectorParams {
connectorParams: ServiceParams<CasesConnectorConfig, CasesConnectorSecrets>;
Expand All @@ -33,6 +35,7 @@ export class CasesConnector extends SubActionConnector<
> {
private readonly casesOracleService: CasesOracleService;
private readonly casesService: CasesService;
private readonly retryService: CaseConnectorRetryService;
private readonly kibanaRequest: KibanaRequest;
private readonly casesParams: CasesConnectorParams['casesParams'];

Expand All @@ -51,6 +54,9 @@ export class CasesConnector extends SubActionConnector<

this.casesService = new CasesService();

const backOffFactory = fullJitterBackoffFactory({ baseDelay: 5, maxBackoffTime: 2000 });
this.retryService = new CaseConnectorRetryService(backOffFactory);

/**
* TODO: Get request from the actions framework.
* Should be set in the SubActionConnector's constructor
Expand Down Expand Up @@ -80,6 +86,14 @@ export class CasesConnector extends SubActionConnector<
}

public async run(params: CasesConnectorRunParams) {
/**
* TODO: Tell the task manager to not retry on non
* retryable errors
*/
await this.retryService.retryWithBackoff(() => this._run(params));
}

private async _run(params: CasesConnectorRunParams) {
try {
const casesClient = await this.casesParams.getCasesClient(this.kibanaRequest);

Expand Down

0 comments on commit 3b9c564

Please sign in to comment.