[Fleet] Enable rollover in custom integrations install when getting mapper_exception error (#186991)

Fixes https://github.com/elastic/kibana/issues/183496
Fixes https://github.com/elastic/kibana/issues/180062

## Summary
Custom integration creation / integration update currently fails on
changing subobjects property on mapping with `mapper_exception` error.
This PR handles this case by triggering a rollover when the error is
received.

## Testing 
- In dev tools, enter the following
 ```
  POST logs-testintegration.test-default/_doc
  {
     "message": "abc"
  }
```
- Navigate to `/app/observabilityOnboarding/customLogs/?category=logs` and fill it with the following:

![Screenshot 2024-06-26 at 15 19 19](a8a0016b-79aa-41b9-9b9b-8a4198a43feb)
In the screenshot the error is visible but in the current branch the request should succeed and install `testintegration` correctly.


### Checklist

- [ ] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios

---------

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Cristina Amico 2024-07-01 19:00:12 +02:00 committed by GitHub
parent 003ff3e601
commit 2b611d8835
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 117 additions and 13 deletions

View file

@ -1805,6 +1805,59 @@ describe('EPM template', () => {
})
);
});
it('should rollover on expected error when field subobjects in mappings changed', async () => {
const esClient = elasticsearchServiceMock.createElasticsearchClient();
esClient.indices.getDataStream.mockResponse({
data_streams: [{ name: 'test.prefix1-default' }],
} as any);
esClient.indices.get.mockResponse({
'test.prefix1-default': {
mappings: {},
},
} as any);
esClient.indices.simulateTemplate.mockResponse({
template: {
settings: { index: {} },
mappings: { subobjects: false },
},
} as any);
esClient.indices.putMapping.mockImplementation(() => {
throw new errors.ResponseError({
body: {
error: {
message:
'mapper_exception\n' +
'\tRoot causes:\n' +
"\t\tmapper_exception: the [subobjects] parameter can't be updated for the object mapping [_doc]",
},
},
} as any);
});
const logger = loggerMock.create();
await updateCurrentWriteIndices(esClient, logger, [
{
templateName: 'test',
indexTemplate: {
index_patterns: ['test.*-*'],
template: {
settings: { index: {} },
mappings: {},
},
} as any,
},
]);
expect(esClient.transport.request).toHaveBeenCalledWith(
expect.objectContaining({
path: '/test.prefix1-default/_rollover',
querystring: {
lazy: true,
},
})
);
});
it('should skip rollover on expected error when flag is on', async () => {
const esClient = elasticsearchServiceMock.createElasticsearchClient();
esClient.indices.getDataStream.mockResponse({

View file

@ -1012,7 +1012,6 @@ const updateExistingDataStream = async ({
const existingDsConfig = Object.values(existingDs);
const currentBackingIndexConfig = existingDsConfig.at(-1);
const currentIndexMode = currentBackingIndexConfig?.settings?.index?.mode;
// @ts-expect-error Property 'mode' does not exist on type 'MappingSourceField'
const currentSourceType = currentBackingIndexConfig.mappings?._source?.mode;
@ -1020,7 +1019,7 @@ const updateExistingDataStream = async ({
let settings: IndicesIndexSettings;
let mappings: MappingTypeMapping;
let lifecycle: any;
let subobjectsFieldChanged: boolean = false;
try {
const simulateResult = await retryTransientEsErrors(async () =>
esClient.indices.simulateTemplate({
@ -1040,7 +1039,9 @@ const updateExistingDataStream = async ({
delete mappings.properties.stream;
delete mappings.properties.data_stream;
}
if (currentBackingIndexConfig?.mappings?.subobjects !== mappings.subobjects) {
subobjectsFieldChanged = true;
}
logger.info(`Attempt to update the mappings for the ${dataStreamName} (write_index_only)`);
await retryTransientEsErrors(
() =>
@ -1055,9 +1056,11 @@ const updateExistingDataStream = async ({
// if update fails, rollover data stream and bail out
} catch (err) {
if (
isResponseError(err) &&
err.statusCode === 400 &&
err.body?.error?.type === 'illegal_argument_exception'
(isResponseError(err) &&
err.statusCode === 400 &&
err.body?.error?.type === 'illegal_argument_exception') ||
// handling the case when subobjects field changed, it should also trigger a rollover
subobjectsFieldChanged
) {
logger.info(`Mappings update for ${dataStreamName} failed due to ${err}`);
if (options?.skipDataStreamRollover === true) {

View file

@ -26,13 +26,12 @@ export default function (providerContext: FtrProviderContext) {
.send({ force: true });
};
// FLAKY: https://github.com/elastic/kibana/issues/180062
describe.skip('Installing custom integrations', async () => {
describe('TESTME Installing custom integrations', async () => {
afterEach(async () => {
await uninstallPackage();
});
it("Correcty installs a custom integration and all of it's assets", async () => {
it('Correcty installs a custom integration and all of its assets', async () => {
const response = await supertest
.post(`/api/fleet/epm/custom_integrations`)
.set('kbn-xsrf', 'xxxx')
@ -170,20 +169,40 @@ export default function (providerContext: FtrProviderContext) {
dynamic_templates: [
{
ecs_timestamp: {
match: '@timestamp',
mapping: {
ignore_malformed: false,
type: 'date',
},
match: '@timestamp',
},
},
{
ecs_message_match_only_text: {
path_match: ['message', '*.message'],
unmatch_mapping_type: 'object',
mapping: {
type: 'match_only_text',
},
path_match: ['message', '*.message'],
unmatch_mapping_type: 'object',
},
},
{
ecs_non_indexed_keyword: {
mapping: {
doc_values: false,
index: false,
type: 'keyword',
},
path_match: 'event.original',
},
},
{
ecs_non_indexed_long: {
mapping: {
doc_values: false,
index: false,
type: 'long',
},
path_match: '*.x509.public_key_exponent',
},
},
{
@ -305,7 +324,7 @@ export default function (providerContext: FtrProviderContext) {
},
{
ecs_geo_point: {
path_match: ['location', '*.location'],
path_match: '*.geo.location',
mapping: {
type: 'geo_point',
},
@ -426,6 +445,7 @@ export default function (providerContext: FtrProviderContext) {
'event.ingested': {
type: 'date',
format: 'strict_date_time_no_millis||strict_date_optional_time||epoch_millis',
ignore_malformed: false,
},
'host.architecture': {
type: 'keyword',
@ -508,6 +528,34 @@ export default function (providerContext: FtrProviderContext) {
});
});
it('Works correctly when there is an existing datastream with the same name', async () => {
const INTEGRATION_NAME_1 = 'myintegration';
const DATASET_NAME = 'test';
await esClient.transport.request({
method: 'POST',
path: `logs-${INTEGRATION_NAME_1}.${DATASET_NAME}-default/_doc`,
body: {
'@timestamp': '2015-01-01',
logs_test_name: `${DATASET_NAME}`,
data_stream: {
dataset: `${INTEGRATION_NAME_1}.${DATASET_NAME}_logs`,
namespace: 'default',
type: 'logs',
},
},
});
await supertest
.post(`/api/fleet/epm/custom_integrations`)
.set('kbn-xsrf', 'xxxx')
.type('application/json')
.send({
force: true,
integrationName: INTEGRATION_NAME_1,
datasets: [{ name: `${INTEGRATION_NAME_1}.${DATASET_NAME}`, type: 'logs' }],
})
.expect(200);
});
it('Throws an error when there is a naming collision with a current package installation', async () => {
await supertest
.post(`/api/fleet/epm/custom_integrations`)