13
13
import org .elasticsearch .common .Strings ;
14
14
import org .elasticsearch .common .network .NetworkAddress ;
15
15
import org .elasticsearch .common .time .DateFormatter ;
16
+ import org .elasticsearch .common .time .FormatNames ;
16
17
import org .elasticsearch .core .Nullable ;
17
18
import org .elasticsearch .core .SuppressForbidden ;
18
19
import org .elasticsearch .test .cluster .ElasticsearchCluster ;
@@ -63,7 +64,7 @@ public class EcsDynamicTemplatesIT extends ESRestTestCase {
63
64
64
65
private static Map <String , Object > ecsDynamicTemplates ;
65
66
private static Map <String , Map <String , Object >> ecsFlatFieldDefinitions ;
66
- private static Map <String , String > ecsFlatMultiFieldDefinitions ;
67
+ private static Map <String , Map < String , Object > > ecsFlatMultiFieldDefinitions ;
67
68
68
69
@ BeforeClass
69
70
public static void setupSuiteScopeCluster () throws Exception {
@@ -142,12 +143,11 @@ private static void prepareEcsDefinitions() throws IOException {
142
143
iterator .remove ();
143
144
}
144
145
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" );
146
147
if (multiFields != null ) {
147
148
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 );
151
151
});
152
152
}
153
153
}
@@ -166,6 +166,22 @@ public void testFlattenedFields() throws IOException {
166
166
verifyEcsMappings (indexName );
167
167
}
168
168
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
+
169
185
public void testFlattenedFieldsWithoutSubobjects () throws IOException {
170
186
String indexName = "test_flattened_fields_subobjects_false" ;
171
187
createTestIndex (indexName , Map .of ("subobjects" , false ));
@@ -191,7 +207,45 @@ public void testNumericMessage() throws IOException {
191
207
verifyEcsMappings (indexName );
192
208
}
193
209
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 ) {
195
249
assertNotNull ("expected to get non-null mappings for field" , actualMappings );
196
250
assertEquals (expectedType , actualMappings .get ("type" ));
197
251
}
@@ -297,6 +351,7 @@ private static void createTestIndex(String indexName, @Nullable Map<String, Obje
297
351
} else {
298
352
indexMappings = ecsDynamicTemplates ;
299
353
}
354
+ indexMappings .put ("date_detection" , false );
300
355
try (XContentBuilder bodyBuilder = JsonXContent .contentBuilder ()) {
301
356
bodyBuilder .startObject ();
302
357
bodyBuilder .startObject ("settings" );
@@ -334,7 +389,7 @@ private Object generateTestValue(String type) {
334
389
return "test" ;
335
390
}
336
391
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 ());
338
393
}
339
394
case "ip" -> {
340
395
return NetworkAddress .format (randomIp (true ));
@@ -395,12 +450,19 @@ private void processRawMappingsSubtree(
395
450
}
396
451
397
452
private void verifyEcsMappings (String indexName ) throws IOException {
453
+ verifyEcsMappings (indexName , "" );
454
+ }
455
+
456
+ private void verifyEcsMappings (String indexName , String fieldPrefix ) throws IOException {
398
457
final Map <String , Object > rawMappings = getMappings (indexName );
399
458
final Map <String , Map <String , Object >> flatFieldMappings = new HashMap <>();
400
459
final Map <String , Map <String , Object >> flatMultiFieldsMappings = new HashMap <>();
401
460
processRawMappingsSubtree (rawMappings , flatFieldMappings , flatMultiFieldsMappings , "" );
402
461
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
+
404
466
logger .info ("Testing mapping of {} ECS fields" , shallowFieldMapCopy .size ());
405
467
List <String > nonEcsFields = new ArrayList <>();
406
468
Map <String , String > fieldToWrongMappingType = new HashMap <>();
@@ -411,32 +473,35 @@ private void verifyEcsMappings(String indexName) throws IOException {
411
473
if (expectedMappings == null ) {
412
474
nonEcsFields .add (fieldName );
413
475
} 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
+ );
425
484
}
426
485
});
427
486
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 ));
429
490
logger .info ("Testing mapping of {} ECS multi-fields" , shallowMultiFieldMapCopy .size ());
430
491
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 ) {
433
494
// not finding an entry in the expected multi-field mappings map is acceptable: our dynamic templates are required to
434
495
// ensure multi-field mapping for all fields with such ECS definitions. However, the patterns in these templates may lead
435
496
// 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
+ );
440
505
}
441
506
});
442
507
@@ -460,7 +525,13 @@ private void verifyEcsMappings(String indexName) throws IOException {
460
525
);
461
526
});
462
527
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" );
464
535
logger .error (
465
536
"ECS field '{}' should be mapped to type '{}' but is mapped to type '{}'. Update {} accordingly." ,
466
537
fieldName ,
@@ -493,4 +564,25 @@ private void verifyEcsMappings(String indexName) throws IOException {
493
564
wronglyDocValuedFields .isEmpty ()
494
565
);
495
566
}
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
+ }
496
588
}
0 commit comments