[7.17] Handle content stream errors in report pre-deletion (#173792) (#174141)

# Backport

This will backport the following commits from `main` to `7.17`:
- [Handle content stream errors in report pre-deletion
(#173792)](https://github.com/elastic/kibana/pull/173792)

<!--- Backport version: 8.9.8 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sqren/backport)

<!--BACKPORT [{"author":{"name":"Tim
Sullivan","email":"tsullivan@users.noreply.github.com"},"sourceCommit":{"committedDate":"2024-01-02T23:00:53Z","message":"Handle
content stream errors in report pre-deletion (#173792)\n\nRe-addresses
https://github.com/elastic/kibana/issues/171363\r\n\r\nThe bug was still
evident, especially when using network throttling to\r\nadd slight lag
to the request turnaround times.\r\n\r\nThis PR adds more handling of
errors that could be thrown slightly prior\r\nto deleting the report
document, when we try to clear all chunks of the\r\nreport using the
content
stream.\r\n\r\n<details>\r\n<summary>Before</summary>\r\n\r\n\r\n\r\n4c1f5edd-73f1-4ca4-a40a-f900ca5f9c78\r\n\r\n\r\n</details>\r\n\r\n###
Checklist\r\n- [x] Unit
tests","sha":"dc813c351fe111c895e85a188372ad31625d8c8c","branchLabelMapping":{"^v8.13.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","backport:prev-minor","backport:prev-MAJOR","v8.12.0","v8.13.0","v8.11.4"],"number":173792,"url":"https://github.com/elastic/kibana/pull/173792","mergeCommit":{"message":"Handle
content stream errors in report pre-deletion (#173792)\n\nRe-addresses
https://github.com/elastic/kibana/issues/171363\r\n\r\nThe bug was still
evident, especially when using network throttling to\r\nadd slight lag
to the request turnaround times.\r\n\r\nThis PR adds more handling of
errors that could be thrown slightly prior\r\nto deleting the report
document, when we try to clear all chunks of the\r\nreport using the
content
stream.\r\n\r\n<details>\r\n<summary>Before</summary>\r\n\r\n\r\n\r\n4c1f5edd-73f1-4ca4-a40a-f900ca5f9c78\r\n\r\n\r\n</details>\r\n\r\n###
Checklist\r\n- [x] Unit
tests","sha":"dc813c351fe111c895e85a188372ad31625d8c8c"}},"sourceBranch":"main","suggestedTargetBranches":["8.12","8.11"],"targetPullRequestStates":[{"branch":"8.12","label":"v8.12.0","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v8.13.0","labelRegex":"^v8.13.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/173792","number":173792,"mergeCommit":{"message":"Handle
content stream errors in report pre-deletion (#173792)\n\nRe-addresses
https://github.com/elastic/kibana/issues/171363\r\n\r\nThe bug was still
evident, especially when using network throttling to\r\nadd slight lag
to the request turnaround times.\r\n\r\nThis PR adds more handling of
errors that could be thrown slightly prior\r\nto deleting the report
document, when we try to clear all chunks of the\r\nreport using the
content
stream.\r\n\r\n<details>\r\n<summary>Before</summary>\r\n\r\n\r\n\r\n4c1f5edd-73f1-4ca4-a40a-f900ca5f9c78\r\n\r\n\r\n</details>\r\n\r\n###
Checklist\r\n- [x] Unit
tests","sha":"dc813c351fe111c895e85a188372ad31625d8c8c"}},{"branch":"8.11","label":"v8.11.4","labelRegex":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->
This commit is contained in:
Tim Sullivan 2024-01-04 11:38:18 -07:00 committed by GitHub
parent 07dc7a1a9f
commit eb687c43d6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 66 additions and 8 deletions

View file

@ -99,7 +99,7 @@ describe('deleteJobResponseHandler', () => {
ReturnType<typeof jobsQuery.get>
>);
jobsQuery.delete.mockRejectedValueOnce(
Object.assign(new Error('Some error.'), { statusCode: 123 })
Object.assign(new Error('Some error.'), { statusCode: 500 })
);
await deleteJobResponseHandler(
core,
@ -110,8 +110,7 @@ describe('deleteJobResponseHandler', () => {
);
expect(response.customError).toHaveBeenCalledWith({
statusCode: 123,
body: 'Some error.',
statusCode: 500,
});
});
});

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import { promisify } from 'util';
import { kibanaResponseFactory } from 'kibana/server';
import { ReportingCore } from '../../';
import { ALLOWED_JOB_CONTENT_TYPES } from '../../../common/constants';
@ -90,18 +89,41 @@ export async function deleteJobResponseHandler(
const docIndex = doc.index;
const stream = await getContentStream(reporting, { id: docId, index: docIndex });
const reportingSetup = reporting.getPluginSetupDeps();
const logger = reportingSetup.logger.clone(['delete-report']);
// An "error" event is emitted if an error is
// passed to the `stream.end` callback from
// the _final method of the ContentStream.
// This event must be handled.
stream.on('error', (err) => {
logger.error(err);
});
try {
/** @note Overwriting existing content with an empty buffer to remove all the chunks. */
await promisify(stream.end.bind(stream, '', 'utf8'))();
// Overwriting existing content with an
// empty buffer to remove all the chunks.
await new Promise<void>((resolve, reject) => {
stream.end('', 'utf8', (error?: Error) => {
if (error) {
// handle error that could be thrown
// from the _write method of the ContentStream
reject(error);
} else {
resolve();
}
});
});
await jobsQuery.delete(docIndex, docId);
return res.ok({
body: { deleted: true },
});
} catch (error) {
logger.error(error);
return res.customError({
statusCode: error.statusCode,
body: error.message,
statusCode: 500,
});
}
}

View file

@ -363,4 +363,41 @@ describe('GET /api/reporting/jobs/download', () => {
);
});
});
describe('delete report', () => {
const getCompleteHits = ({
jobType = 'unencodedJobType',
outputContentType = 'text/plain',
title = '',
} = {}) => {
return getHits({
jobtype: jobType,
status: 'completed',
output: { content_type: outputContentType },
payload: { title },
});
};
it('handles content stream errors', async () => {
stream = new Readable({
read() {
this.push('test');
this.push(null);
},
}) as typeof stream;
stream.end = jest.fn().mockImplementation((_name, _encoding, callback) => {
callback(new Error('An error occurred in ending the content stream'));
});
(getContentStream as jest.MockedFunction<typeof getContentStream>).mockResolvedValue(stream);
mockEsClient.search.mockResolvedValueOnce({ body: getCompleteHits() } as any);
registerJobInfoRoutes(core);
await server.start();
await supertest(httpSetup.server.listener)
.delete('/api/reporting/jobs/delete/denk')
.expect(500)
.expect('Content-Type', 'application/json; charset=utf-8');
});
});
});