mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
[8.15] [UII] Fill in empty values for `constant_keyword` fields from existing mappings (#188145) (#188170)
# Backport This will backport the following commits from `main` to `8.15`: - [[UII] Fill in empty values for `constant_keyword` fields from existing mappings (#188145)](https://github.com/elastic/kibana/pull/188145) <!--- Backport version: 9.4.3 --> ### Questions ? Please refer to the [Backport tool documentation](https://github.com/sqren/backport) <!--BACKPORT [{"author":{"name":"Jen Huang","email":"its.jenetic@gmail.com"},"sourceCommit":{"committedDate":"2024-07-12T03:05:03Z","message":"[UII] Fill in empty values for `constant_keyword` fields from existing mappings (#188145)\n\n## Summary\r\n\r\nResolves https://github.com/elastic/kibana/issues/178528.\r\n\r\nSome packages declare `constant_keyword` type fields without an explicit\r\nvalue. This causes ES to fill in the value in the mappings using the\r\nfirst ingested value.\r\n\r\nWhen upgrading this type of package & field after the value has already\r\nbeen populated in this way, the mappings update fail due to pushing a\r\n`null` value into an existing value, triggering unnecessary rollovers.\r\n\r\nThis PR fixes that by filling in the empty values from the existing\r\nmappings.\r\n\r\n## Test\r\n1. On an empty cluster, turn on debug logs\r\n2. Set up Fleet Server policy and Fleet Server agent\r\n3. Force install old version of Elastic Agent integration, v1.19.2:\r\n```\r\nPOST kbn:/api/fleet/epm/packages/elastic_agent/1.19.2\r\n{\r\n \"force\": true\r\n}\r\n```\r\n4. Create a new empty policy, **deselect system and agent monitoring**\r\n(otherwise the integration will be upgraded, we do not want this yet)\r\n5. Manually add Elastic Agent integration v1.19.2 to the new policy\r\n6. Edit the policy to enable logs and metrics monitoring\r\n7. Enroll agent into the policy, confirm that monitoring logs and\r\nmetrics are being ingested and that a value exists for `event.dataset`\r\nmapping for the logs:\r\n```\r\nGET logs-elastic_agent*/_mappings\r\n```\r\n```\r\n \"dataset\": {\r\n \"type\": \"constant_keyword\",\r\n \"value\": \"elastic_agent\"\r\n }\r\n```\r\n9. Upgrade Elastic Agent integration to v1.20.0 (note we are not\r\nupgrading to the newest versions, 2.0+, because these **are** expected\r\nto trigger rollovers for some data streams):\r\n```\r\nPOST kbn:/api/fleet/epm/packages/elastic_agent/1.20.0\r\n{\r\n \"force\": true\r\n}\r\n```\r\n10. Confirm in Kibana logs that no rollovers triggered during the\r\nupgrade\r\n11. Confirm that there is still only 1 backing index for monitoring\r\nlogs:\r\n```\r\nGET logs-elastic_agent*\r\n```\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"b7c96f4c09e88b820664bbd0bb996844dd50a0e6","branchLabelMapping":{"^v8.16.0$":"main","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:fix","Team:Fleet","backport:prev-minor","v8.16.0"],"title":"[UII] Fill in empty values for `constant_keyword` fields from existing mappings","number":188145,"url":"https://github.com/elastic/kibana/pull/188145","mergeCommit":{"message":"[UII] Fill in empty values for `constant_keyword` fields from existing mappings (#188145)\n\n## Summary\r\n\r\nResolves https://github.com/elastic/kibana/issues/178528.\r\n\r\nSome packages declare `constant_keyword` type fields without an explicit\r\nvalue. This causes ES to fill in the value in the mappings using the\r\nfirst ingested value.\r\n\r\nWhen upgrading this type of package & field after the value has already\r\nbeen populated in this way, the mappings update fail due to pushing a\r\n`null` value into an existing value, triggering unnecessary rollovers.\r\n\r\nThis PR fixes that by filling in the empty values from the existing\r\nmappings.\r\n\r\n## Test\r\n1. On an empty cluster, turn on debug logs\r\n2. Set up Fleet Server policy and Fleet Server agent\r\n3. Force install old version of Elastic Agent integration, v1.19.2:\r\n```\r\nPOST kbn:/api/fleet/epm/packages/elastic_agent/1.19.2\r\n{\r\n \"force\": true\r\n}\r\n```\r\n4. Create a new empty policy, **deselect system and agent monitoring**\r\n(otherwise the integration will be upgraded, we do not want this yet)\r\n5. Manually add Elastic Agent integration v1.19.2 to the new policy\r\n6. Edit the policy to enable logs and metrics monitoring\r\n7. Enroll agent into the policy, confirm that monitoring logs and\r\nmetrics are being ingested and that a value exists for `event.dataset`\r\nmapping for the logs:\r\n```\r\nGET logs-elastic_agent*/_mappings\r\n```\r\n```\r\n \"dataset\": {\r\n \"type\": \"constant_keyword\",\r\n \"value\": \"elastic_agent\"\r\n }\r\n```\r\n9. Upgrade Elastic Agent integration to v1.20.0 (note we are not\r\nupgrading to the newest versions, 2.0+, because these **are** expected\r\nto trigger rollovers for some data streams):\r\n```\r\nPOST kbn:/api/fleet/epm/packages/elastic_agent/1.20.0\r\n{\r\n \"force\": true\r\n}\r\n```\r\n10. Confirm in Kibana logs that no rollovers triggered during the\r\nupgrade\r\n11. Confirm that there is still only 1 backing index for monitoring\r\nlogs:\r\n```\r\nGET logs-elastic_agent*\r\n```\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"b7c96f4c09e88b820664bbd0bb996844dd50a0e6"}},"sourceBranch":"main","suggestedTargetBranches":[],"targetPullRequestStates":[{"branch":"main","label":"v8.16.0","branchLabelMappingKey":"^v8.16.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/188145","number":188145,"mergeCommit":{"message":"[UII] Fill in empty values for `constant_keyword` fields from existing mappings (#188145)\n\n## Summary\r\n\r\nResolves https://github.com/elastic/kibana/issues/178528.\r\n\r\nSome packages declare `constant_keyword` type fields without an explicit\r\nvalue. This causes ES to fill in the value in the mappings using the\r\nfirst ingested value.\r\n\r\nWhen upgrading this type of package & field after the value has already\r\nbeen populated in this way, the mappings update fail due to pushing a\r\n`null` value into an existing value, triggering unnecessary rollovers.\r\n\r\nThis PR fixes that by filling in the empty values from the existing\r\nmappings.\r\n\r\n## Test\r\n1. On an empty cluster, turn on debug logs\r\n2. Set up Fleet Server policy and Fleet Server agent\r\n3. Force install old version of Elastic Agent integration, v1.19.2:\r\n```\r\nPOST kbn:/api/fleet/epm/packages/elastic_agent/1.19.2\r\n{\r\n \"force\": true\r\n}\r\n```\r\n4. Create a new empty policy, **deselect system and agent monitoring**\r\n(otherwise the integration will be upgraded, we do not want this yet)\r\n5. Manually add Elastic Agent integration v1.19.2 to the new policy\r\n6. Edit the policy to enable logs and metrics monitoring\r\n7. Enroll agent into the policy, confirm that monitoring logs and\r\nmetrics are being ingested and that a value exists for `event.dataset`\r\nmapping for the logs:\r\n```\r\nGET logs-elastic_agent*/_mappings\r\n```\r\n```\r\n \"dataset\": {\r\n \"type\": \"constant_keyword\",\r\n \"value\": \"elastic_agent\"\r\n }\r\n```\r\n9. Upgrade Elastic Agent integration to v1.20.0 (note we are not\r\nupgrading to the newest versions, 2.0+, because these **are** expected\r\nto trigger rollovers for some data streams):\r\n```\r\nPOST kbn:/api/fleet/epm/packages/elastic_agent/1.20.0\r\n{\r\n \"force\": true\r\n}\r\n```\r\n10. Confirm in Kibana logs that no rollovers triggered during the\r\nupgrade\r\n11. Confirm that there is still only 1 backing index for monitoring\r\nlogs:\r\n```\r\nGET logs-elastic_agent*\r\n```\r\n\r\n### Checklist\r\n\r\nDelete any items that are not applicable to this PR.\r\n\r\n- [x] [Unit or functional\r\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\r\nwere updated or added to match the most common scenarios","sha":"b7c96f4c09e88b820664bbd0bb996844dd50a0e6"}}]}] BACKPORT--> Co-authored-by: Jen Huang <its.jenetic@gmail.com>
This commit is contained in:
parent
d8611f1b13
commit
6302a65c80
3 changed files with 270 additions and 6 deletions
|
@ -39,7 +39,7 @@ import { retryTransientEsErrors } from '../retry';
|
|||
import { PackageESError, PackageInvalidArchiveError } from '../../../../errors';
|
||||
|
||||
import { getDefaultProperties, histogram, keyword, scaledFloat } from './mappings';
|
||||
import { isUserSettingsTemplate } from './utils';
|
||||
import { isUserSettingsTemplate, fillConstantKeywordValues } from './utils';
|
||||
|
||||
interface Properties {
|
||||
[key: string]: any;
|
||||
|
@ -986,7 +986,7 @@ const updateAllDataStreams = async (
|
|||
});
|
||||
},
|
||||
{
|
||||
// Limit concurrent putMapping/rollover requests to avoid overhwhelming ES cluster
|
||||
// Limit concurrent putMapping/rollover requests to avoid overwhelming ES cluster
|
||||
concurrency: 20,
|
||||
}
|
||||
);
|
||||
|
@ -1017,19 +1017,23 @@ const updateExistingDataStream = async ({
|
|||
const currentSourceType = currentBackingIndexConfig.mappings?._source?.mode;
|
||||
|
||||
let settings: IndicesIndexSettings;
|
||||
let mappings: MappingTypeMapping;
|
||||
let mappings: MappingTypeMapping = {};
|
||||
let lifecycle: any;
|
||||
let subobjectsFieldChanged: boolean = false;
|
||||
let simulateResult: any = {};
|
||||
try {
|
||||
const simulateResult = await retryTransientEsErrors(async () =>
|
||||
simulateResult = await retryTransientEsErrors(async () =>
|
||||
esClient.indices.simulateTemplate({
|
||||
name: await getIndexTemplate(esClient, dataStreamName),
|
||||
})
|
||||
);
|
||||
|
||||
settings = simulateResult.template.settings;
|
||||
mappings = simulateResult.template.mappings;
|
||||
// @ts-expect-error template is not yet typed with DLM
|
||||
mappings = fillConstantKeywordValues(
|
||||
currentBackingIndexConfig?.mappings || {},
|
||||
simulateResult.template.mappings
|
||||
);
|
||||
|
||||
lifecycle = simulateResult.template.lifecycle;
|
||||
|
||||
// for now, remove from object so as not to update stream or data stream properties of the index until type and name
|
||||
|
@ -1063,6 +1067,7 @@ const updateExistingDataStream = async ({
|
|||
subobjectsFieldChanged
|
||||
) {
|
||||
logger.info(`Mappings update for ${dataStreamName} failed due to ${err}`);
|
||||
logger.trace(`Attempted mappings: ${mappings}`);
|
||||
if (options?.skipDataStreamRollover === true) {
|
||||
logger.info(
|
||||
`Skipping rollover for ${dataStreamName} as "skipDataStreamRollover" is enabled`
|
||||
|
@ -1075,6 +1080,7 @@ const updateExistingDataStream = async ({
|
|||
}
|
||||
}
|
||||
logger.error(`Mappings update for ${dataStreamName} failed due to unexpected error: ${err}`);
|
||||
logger.trace(`Attempted mappings: ${mappings}`);
|
||||
if (options?.ignoreMappingUpdateErrors === true) {
|
||||
logger.info(`Ignore mapping update errors as "ignoreMappingUpdateErrors" is enabled`);
|
||||
return;
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* 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 { fillConstantKeywordValues } from './utils';
|
||||
|
||||
describe('fillConstantKeywordValues', () => {
|
||||
const oldMappings = {
|
||||
dynamic: false,
|
||||
_meta: {
|
||||
managed_by: 'fleet',
|
||||
managed: true,
|
||||
package: {
|
||||
name: 'elastic_agent',
|
||||
},
|
||||
},
|
||||
dynamic_templates: [
|
||||
{
|
||||
ecs_timestamp: {
|
||||
match: '@timestamp',
|
||||
mapping: {
|
||||
ignore_malformed: false,
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
date_detection: false,
|
||||
properties: {
|
||||
'@timestamp': {
|
||||
type: 'date',
|
||||
ignore_malformed: false,
|
||||
},
|
||||
load: {
|
||||
properties: {
|
||||
'1': {
|
||||
type: 'double',
|
||||
},
|
||||
'5': {
|
||||
type: 'double',
|
||||
},
|
||||
'15': {
|
||||
type: 'double',
|
||||
},
|
||||
},
|
||||
},
|
||||
event: {
|
||||
properties: {
|
||||
agent_id_status: {
|
||||
type: 'keyword',
|
||||
ignore_above: 1024,
|
||||
},
|
||||
dataset: {
|
||||
type: 'constant_keyword',
|
||||
value: 'elastic_agent.metricbeat',
|
||||
},
|
||||
ingested: {
|
||||
type: 'date',
|
||||
format: 'strict_date_time_no_millis||strict_date_optional_time||epoch_millis',
|
||||
ignore_malformed: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
message: {
|
||||
type: 'match_only_text',
|
||||
},
|
||||
'dot.field': {
|
||||
type: 'keyword',
|
||||
},
|
||||
constant_keyword_without_value: {
|
||||
type: 'constant_keyword',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const newMappings = {
|
||||
dynamic: false,
|
||||
_meta: {
|
||||
managed_by: 'fleet',
|
||||
managed: true,
|
||||
package: {
|
||||
name: 'elastic_agent',
|
||||
},
|
||||
},
|
||||
dynamic_templates: [
|
||||
{
|
||||
ecs_timestamp: {
|
||||
match: '@timestamp',
|
||||
mapping: {
|
||||
ignore_malformed: false,
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
date_detection: false,
|
||||
properties: {
|
||||
'@timestamp': {
|
||||
type: 'date',
|
||||
ignore_malformed: false,
|
||||
},
|
||||
load: {
|
||||
properties: {
|
||||
'1': {
|
||||
type: 'double',
|
||||
},
|
||||
'5': {
|
||||
type: 'double',
|
||||
},
|
||||
'15': {
|
||||
type: 'double',
|
||||
},
|
||||
},
|
||||
},
|
||||
event: {
|
||||
properties: {
|
||||
agent_id_status: {
|
||||
type: 'keyword',
|
||||
ignore_above: 1024,
|
||||
},
|
||||
dataset: {
|
||||
type: 'constant_keyword',
|
||||
},
|
||||
ingested: {
|
||||
type: 'date',
|
||||
format: 'strict_date_time_no_millis||strict_date_optional_time||epoch_millis',
|
||||
ignore_malformed: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
message: {
|
||||
type: 'match_only_text',
|
||||
},
|
||||
'dot.field': {
|
||||
type: 'keyword',
|
||||
},
|
||||
some_new_field: {
|
||||
type: 'keyword',
|
||||
},
|
||||
constant_keyword_without_value: {
|
||||
type: 'constant_keyword',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
it('should fill in missing constant_keyword values from old mappings correctly', () => {
|
||||
// @ts-ignore
|
||||
expect(fillConstantKeywordValues(oldMappings, newMappings)).toEqual({
|
||||
dynamic: false,
|
||||
_meta: {
|
||||
managed_by: 'fleet',
|
||||
managed: true,
|
||||
package: {
|
||||
name: 'elastic_agent',
|
||||
},
|
||||
},
|
||||
dynamic_templates: [
|
||||
{
|
||||
ecs_timestamp: {
|
||||
match: '@timestamp',
|
||||
mapping: {
|
||||
ignore_malformed: false,
|
||||
type: 'date',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
date_detection: false,
|
||||
properties: {
|
||||
'@timestamp': {
|
||||
type: 'date',
|
||||
ignore_malformed: false,
|
||||
},
|
||||
load: {
|
||||
properties: {
|
||||
'1': {
|
||||
type: 'double',
|
||||
},
|
||||
'5': {
|
||||
type: 'double',
|
||||
},
|
||||
'15': {
|
||||
type: 'double',
|
||||
},
|
||||
},
|
||||
},
|
||||
event: {
|
||||
properties: {
|
||||
agent_id_status: {
|
||||
type: 'keyword',
|
||||
ignore_above: 1024,
|
||||
},
|
||||
dataset: {
|
||||
type: 'constant_keyword',
|
||||
value: 'elastic_agent.metricbeat',
|
||||
},
|
||||
ingested: {
|
||||
type: 'date',
|
||||
format: 'strict_date_time_no_millis||strict_date_optional_time||epoch_millis',
|
||||
ignore_malformed: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
message: {
|
||||
type: 'match_only_text',
|
||||
},
|
||||
'dot.field': {
|
||||
type: 'keyword',
|
||||
},
|
||||
some_new_field: {
|
||||
type: 'keyword',
|
||||
},
|
||||
constant_keyword_without_value: {
|
||||
type: 'constant_keyword',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the same mappings if old mappings are not provided', () => {
|
||||
// @ts-ignore
|
||||
expect(fillConstantKeywordValues({}, newMappings)).toMatchObject(newMappings);
|
||||
});
|
||||
});
|
|
@ -4,6 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { MappingTypeMapping } from '@elastic/elasticsearch/lib/api/typesWithBodyKey';
|
||||
|
||||
import { USER_SETTINGS_TEMPLATE_SUFFIX } from '../../../../constants';
|
||||
|
||||
|
@ -12,3 +13,34 @@ type UserSettingsTemplateName = `${TemplateBaseName}${typeof USER_SETTINGS_TEMPL
|
|||
|
||||
export const isUserSettingsTemplate = (name: string): name is UserSettingsTemplateName =>
|
||||
name.endsWith(USER_SETTINGS_TEMPLATE_SUFFIX);
|
||||
|
||||
// For any `constant_keyword` fields in `newMappings` that don't have a `value`, access the same field in
|
||||
// the `oldMappings` and fill in the value from there
|
||||
export const fillConstantKeywordValues = (
|
||||
oldMappings: MappingTypeMapping,
|
||||
newMappings: MappingTypeMapping
|
||||
) => {
|
||||
const filledMappings = JSON.parse(JSON.stringify(newMappings)) as MappingTypeMapping;
|
||||
const deepGet = (obj: any, keys: string[]) => keys.reduce((xs, x) => xs?.[x] ?? undefined, obj);
|
||||
|
||||
const fillEmptyConstantKeywordFields = (mappings: unknown, currentPath: string[] = []) => {
|
||||
if (!mappings) return;
|
||||
for (const [key, potentialField] of Object.entries(mappings)) {
|
||||
const path = [...currentPath, key];
|
||||
if (typeof potentialField === 'object') {
|
||||
if (potentialField.type === 'constant_keyword' && potentialField.value === undefined) {
|
||||
const valueFromOldMappings = deepGet(oldMappings.properties, [...path, 'value']);
|
||||
if (valueFromOldMappings !== undefined) {
|
||||
potentialField.value = valueFromOldMappings;
|
||||
}
|
||||
} else if (potentialField.properties && typeof potentialField.properties === 'object') {
|
||||
fillEmptyConstantKeywordFields(potentialField.properties, [...path, 'properties']);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
fillEmptyConstantKeywordFields(filledMappings.properties);
|
||||
|
||||
return filledMappings;
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue