Skip to content

Commit 781e197

Browse files
authored
Full coverage of ECS by ecs@mappings when date_detection is disabled (elastic#112444) (elastic#112669)
1 parent 6285860 commit 781e197

File tree

4 files changed

+131
-29
lines changed

4 files changed

+131
-29
lines changed

docs/changelog/112444.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
pr: 112444
2+
summary: Full coverage of ECS by ecs@mappings when `date_detection` is disabled
3+
area: Mapping
4+
type: bug
5+
issues:
6+
- 112398

x-pack/plugin/core/template-resources/src/main/resources/[email protected]

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,11 @@
155155
"ingested",
156156
"*.ingested",
157157
"*.start",
158-
"*.end"
158+
"*.end",
159+
"*.indicator.first_seen",
160+
"*.indicator.last_seen",
161+
"*.indicator.modified_at",
162+
"*threat.enrichments.matched.occurred"
159163
],
160164
"unmatch_mapping_type": "object"
161165
}

x-pack/plugin/stack/src/javaRestTest/java/org/elasticsearch/xpack/stack/EcsDynamicTemplatesIT.java

Lines changed: 119 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import org.elasticsearch.common.Strings;
1414
import org.elasticsearch.common.network.NetworkAddress;
1515
import org.elasticsearch.common.time.DateFormatter;
16+
import org.elasticsearch.common.time.FormatNames;
1617
import org.elasticsearch.core.Nullable;
1718
import org.elasticsearch.core.SuppressForbidden;
1819
import org.elasticsearch.test.cluster.ElasticsearchCluster;
@@ -63,7 +64,7 @@ public class EcsDynamicTemplatesIT extends ESRestTestCase {
6364

6465
private static Map<String, Object> ecsDynamicTemplates;
6566
private static Map<String, Map<String, Object>> ecsFlatFieldDefinitions;
66-
private static Map<String, String> ecsFlatMultiFieldDefinitions;
67+
private static Map<String, Map<String, Object>> ecsFlatMultiFieldDefinitions;
6768

6869
@BeforeClass
6970
public static void setupSuiteScopeCluster() throws Exception {
@@ -142,12 +143,11 @@ private static void prepareEcsDefinitions() throws IOException {
142143
iterator.remove();
143144
}
144145

145-
List<Map<String, String>> multiFields = (List<Map<String, String>>) definitions.get("multi_fields");
146+
List<Map<String, Object>> multiFields = (List<Map<String, Object>>) definitions.get("multi_fields");
146147
if (multiFields != null) {
147148
multiFields.forEach(multiFieldsDefinitions -> {
148-
String subfieldFlatName = Objects.requireNonNull(multiFieldsDefinitions.get("flat_name"));
149-
String subfieldType = Objects.requireNonNull(multiFieldsDefinitions.get("type"));
150-
ecsFlatMultiFieldDefinitions.put(subfieldFlatName, subfieldType);
149+
String subfieldFlatName = (String) Objects.requireNonNull(multiFieldsDefinitions.get("flat_name"));
150+
ecsFlatMultiFieldDefinitions.put(subfieldFlatName, multiFieldsDefinitions);
151151
});
152152
}
153153
}
@@ -166,6 +166,22 @@ public void testFlattenedFields() throws IOException {
166166
verifyEcsMappings(indexName);
167167
}
168168

169+
public void testFlattenedFieldsWithinAttributes() throws IOException {
170+
String indexName = "test-flattened-attributes";
171+
createTestIndex(indexName);
172+
Map<String, Object> flattenedFieldsMap = createTestDocument(true);
173+
indexDocument(indexName, Map.of("attributes", flattenedFieldsMap));
174+
verifyEcsMappings(indexName, "attributes.");
175+
}
176+
177+
public void testFlattenedFieldsWithinResourceAttributes() throws IOException {
178+
String indexName = "test-flattened-attributes";
179+
createTestIndex(indexName);
180+
Map<String, Object> flattenedFieldsMap = createTestDocument(true);
181+
indexDocument(indexName, Map.of("resource.attributes", flattenedFieldsMap));
182+
verifyEcsMappings(indexName, "resource.attributes.");
183+
}
184+
169185
public void testFlattenedFieldsWithoutSubobjects() throws IOException {
170186
String indexName = "test_flattened_fields_subobjects_false";
171187
createTestIndex(indexName, Map.of("subobjects", false));
@@ -191,7 +207,45 @@ public void testNumericMessage() throws IOException {
191207
verifyEcsMappings(indexName);
192208
}
193209

194-
private void assertType(String expectedType, Map<String, Object> actualMappings) throws IOException {
210+
public void testDateFieldsWithDifferentFormats() throws IOException {
211+
Map<String, Object> dateFieldsMap = ecsFlatFieldDefinitions.entrySet()
212+
.stream()
213+
.filter(entry -> "date".equals(entry.getValue().get("type")))
214+
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
215+
216+
// test with iso8601 format
217+
String indexName = "test-date-fields-as-is8601";
218+
createTestIndex(indexName);
219+
Map<String, Object> document = new HashMap<>();
220+
DateFormatter formatter = DateFormatter.forPattern(FormatNames.ISO8601.getName());
221+
for (String field : dateFieldsMap.keySet()) {
222+
document.put(field, formatter.formatMillis(System.currentTimeMillis()));
223+
}
224+
verifyAllDateFields(indexName, document, dateFieldsMap);
225+
226+
// test with milliseconds since epoch format
227+
indexName = "test-date-fields-as-millis";
228+
createTestIndex(indexName);
229+
document = new HashMap<>();
230+
for (String field : dateFieldsMap.keySet()) {
231+
document.put(field, System.currentTimeMillis());
232+
}
233+
verifyAllDateFields(indexName, document, dateFieldsMap);
234+
}
235+
236+
private void verifyAllDateFields(String indexName, Map<String, Object> document, Map<String, Object> dateFieldsMap) throws IOException {
237+
indexDocument(indexName, document);
238+
final Map<String, Object> rawMappings = getMappings(indexName);
239+
final Map<String, Map<String, Object>> flatFieldMappings = new HashMap<>();
240+
processRawMappingsSubtree(rawMappings, flatFieldMappings, new HashMap<>(), "");
241+
flatFieldMappings.forEach((fieldName, fieldMappings) -> {
242+
if (dateFieldsMap.containsKey(fieldName)) {
243+
assertType("date", fieldMappings);
244+
}
245+
});
246+
}
247+
248+
private void assertType(String expectedType, Map<String, Object> actualMappings) {
195249
assertNotNull("expected to get non-null mappings for field", actualMappings);
196250
assertEquals(expectedType, actualMappings.get("type"));
197251
}
@@ -297,6 +351,7 @@ private static void createTestIndex(String indexName, @Nullable Map<String, Obje
297351
} else {
298352
indexMappings = ecsDynamicTemplates;
299353
}
354+
indexMappings.put("date_detection", false);
300355
try (XContentBuilder bodyBuilder = JsonXContent.contentBuilder()) {
301356
bodyBuilder.startObject();
302357
bodyBuilder.startObject("settings");
@@ -334,7 +389,7 @@ private Object generateTestValue(String type) {
334389
return "test";
335390
}
336391
case "date" -> {
337-
return DateFormatter.forPattern("strict_date_optional_time").formatMillis(System.currentTimeMillis());
392+
return DateFormatter.forPattern(FormatNames.STRICT_DATE_OPTIONAL_TIME.getName()).formatMillis(System.currentTimeMillis());
338393
}
339394
case "ip" -> {
340395
return NetworkAddress.format(randomIp(true));
@@ -395,12 +450,19 @@ private void processRawMappingsSubtree(
395450
}
396451

397452
private void verifyEcsMappings(String indexName) throws IOException {
453+
verifyEcsMappings(indexName, "");
454+
}
455+
456+
private void verifyEcsMappings(String indexName, String fieldPrefix) throws IOException {
398457
final Map<String, Object> rawMappings = getMappings(indexName);
399458
final Map<String, Map<String, Object>> flatFieldMappings = new HashMap<>();
400459
final Map<String, Map<String, Object>> flatMultiFieldsMappings = new HashMap<>();
401460
processRawMappingsSubtree(rawMappings, flatFieldMappings, flatMultiFieldsMappings, "");
402461

403-
Map<String, Map<String, Object>> shallowFieldMapCopy = new HashMap<>(ecsFlatFieldDefinitions);
462+
Map<String, Map<String, Object>> shallowFieldMapCopy = ecsFlatFieldDefinitions.entrySet()
463+
.stream()
464+
.collect(Collectors.toMap(e -> fieldPrefix + e.getKey(), Map.Entry::getValue));
465+
404466
logger.info("Testing mapping of {} ECS fields", shallowFieldMapCopy.size());
405467
List<String> nonEcsFields = new ArrayList<>();
406468
Map<String, String> fieldToWrongMappingType = new HashMap<>();
@@ -411,32 +473,35 @@ private void verifyEcsMappings(String indexName) throws IOException {
411473
if (expectedMappings == null) {
412474
nonEcsFields.add(fieldName);
413475
} else {
414-
String expectedType = (String) expectedMappings.get("type");
415-
String actualMappingType = (String) actualMappings.get("type");
416-
if (actualMappingType.equals(expectedType) == false) {
417-
fieldToWrongMappingType.put(fieldName, actualMappingType);
418-
}
419-
if (expectedMappings.get("index") != actualMappings.get("index")) {
420-
wronglyIndexedFields.add(fieldName);
421-
}
422-
if (expectedMappings.get("doc_values") != actualMappings.get("doc_values")) {
423-
wronglyDocValuedFields.add(fieldName);
424-
}
476+
compareExpectedToActualMappings(
477+
fieldName,
478+
actualMappings,
479+
expectedMappings,
480+
fieldToWrongMappingType,
481+
wronglyIndexedFields,
482+
wronglyDocValuedFields
483+
);
425484
}
426485
});
427486

428-
Map<String, String> shallowMultiFieldMapCopy = new HashMap<>(ecsFlatMultiFieldDefinitions);
487+
Map<String, Map<String, Object>> shallowMultiFieldMapCopy = ecsFlatMultiFieldDefinitions.entrySet()
488+
.stream()
489+
.collect(Collectors.toMap(e -> fieldPrefix + e.getKey(), Map.Entry::getValue));
429490
logger.info("Testing mapping of {} ECS multi-fields", shallowMultiFieldMapCopy.size());
430491
flatMultiFieldsMappings.forEach((fieldName, actualMappings) -> {
431-
String expectedType = shallowMultiFieldMapCopy.remove(fieldName);
432-
if (expectedType != null) {
492+
Map<String, Object> expectedMultiFieldMappings = shallowMultiFieldMapCopy.remove(fieldName);
493+
if (expectedMultiFieldMappings != null) {
433494
// not finding an entry in the expected multi-field mappings map is acceptable: our dynamic templates are required to
434495
// ensure multi-field mapping for all fields with such ECS definitions. However, the patterns in these templates may lead
435496
// to multi-field mapping for ECS fields for which such are not defined
436-
String actualMappingType = (String) actualMappings.get("type");
437-
if (actualMappingType.equals(expectedType) == false) {
438-
fieldToWrongMappingType.put(fieldName, actualMappingType);
439-
}
497+
compareExpectedToActualMappings(
498+
fieldName,
499+
actualMappings,
500+
expectedMultiFieldMappings,
501+
fieldToWrongMappingType,
502+
wronglyIndexedFields,
503+
wronglyDocValuedFields
504+
);
440505
}
441506
});
442507

@@ -460,7 +525,13 @@ private void verifyEcsMappings(String indexName) throws IOException {
460525
);
461526
});
462527
fieldToWrongMappingType.forEach((fieldName, actualMappingType) -> {
463-
String ecsExpectedType = (String) ecsFlatFieldDefinitions.get(fieldName).get("type");
528+
// if fieldPrefix is not null, we need to remove it from the field name for the ECS lookup
529+
String ecsFieldName = fieldPrefix == null ? fieldName : fieldName.substring(fieldPrefix.length());
530+
Map<String, Object> fieldMappings = ecsFlatFieldDefinitions.get(ecsFieldName);
531+
if (fieldMappings == null) {
532+
fieldMappings = ecsFlatMultiFieldDefinitions.get(ecsFieldName);
533+
}
534+
String ecsExpectedType = (String) fieldMappings.get("type");
464535
logger.error(
465536
"ECS field '{}' should be mapped to type '{}' but is mapped to type '{}'. Update {} accordingly.",
466537
fieldName,
@@ -493,4 +564,25 @@ private void verifyEcsMappings(String indexName) throws IOException {
493564
wronglyDocValuedFields.isEmpty()
494565
);
495566
}
567+
568+
private static void compareExpectedToActualMappings(
569+
String fieldName,
570+
Map<String, Object> actualMappings,
571+
Map<String, Object> expectedMappings,
572+
Map<String, String> fieldToWrongMappingType,
573+
List<String> wronglyIndexedFields,
574+
List<String> wronglyDocValuedFields
575+
) {
576+
String expectedType = (String) expectedMappings.get("type");
577+
String actualMappingType = (String) actualMappings.get("type");
578+
if (actualMappingType.equals(expectedType) == false) {
579+
fieldToWrongMappingType.put(fieldName, actualMappingType);
580+
}
581+
if (expectedMappings.get("index") != actualMappings.get("index")) {
582+
wronglyIndexedFields.add(fieldName);
583+
}
584+
if (expectedMappings.get("doc_values") != actualMappings.get("doc_values")) {
585+
wronglyDocValuedFields.add(fieldName);
586+
}
587+
}
496588
}

x-pack/plugin/stack/src/main/java/org/elasticsearch/xpack/stack/StackTemplateRegistry.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public class StackTemplateRegistry extends IndexTemplateRegistry {
4848

4949
// The stack template registry version. This number must be incremented when we make changes
5050
// to built-in templates.
51-
public static final int REGISTRY_VERSION = 12;
51+
public static final int REGISTRY_VERSION = 14;
5252

5353
public static final String TEMPLATE_VERSION_VARIABLE = "xpack.stack.template.version";
5454
public static final Setting<Boolean> STACK_TEMPLATES_ENABLED = Setting.boolSetting(

0 commit comments

Comments
 (0)