|
|
@@ -0,0 +1,413 @@
|
|
|
+/*
|
|
|
+ * 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.
|
|
|
+ */
|
|
|
+
|
|
|
+package org.elasticsearch.xpack.stack;
|
|
|
+
|
|
|
+import org.apache.http.HttpStatus;
|
|
|
+import org.elasticsearch.client.Request;
|
|
|
+import org.elasticsearch.client.Response;
|
|
|
+import org.elasticsearch.common.Strings;
|
|
|
+import org.elasticsearch.common.network.NetworkAddress;
|
|
|
+import org.elasticsearch.common.time.DateFormatter;
|
|
|
+import org.elasticsearch.core.Nullable;
|
|
|
+import org.elasticsearch.core.SuppressForbidden;
|
|
|
+import org.elasticsearch.test.cluster.ElasticsearchCluster;
|
|
|
+import org.elasticsearch.test.rest.ESRestTestCase;
|
|
|
+import org.elasticsearch.xcontent.XContentBuilder;
|
|
|
+import org.elasticsearch.xcontent.XContentFactory;
|
|
|
+import org.elasticsearch.xcontent.XContentParser;
|
|
|
+import org.elasticsearch.xcontent.XContentParserConfiguration;
|
|
|
+import org.elasticsearch.xcontent.XContentType;
|
|
|
+import org.elasticsearch.xcontent.json.JsonXContent;
|
|
|
+import org.elasticsearch.xpack.core.template.TemplateUtils;
|
|
|
+import org.junit.BeforeClass;
|
|
|
+import org.junit.ClassRule;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.io.InputStream;
|
|
|
+import java.net.URL;
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.HashMap;
|
|
|
+import java.util.Iterator;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Locale;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.Objects;
|
|
|
+import java.util.Set;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+import java.util.stream.Stream;
|
|
|
+
|
|
|
+import static org.hamcrest.Matchers.equalTo;
|
|
|
+import static org.hamcrest.Matchers.instanceOf;
|
|
|
+
|
|
|
+@SuppressWarnings("unchecked")
|
|
|
+public class EcsDynamicTemplatesIT extends ESRestTestCase {
|
|
|
+
|
|
|
+ @ClassRule
|
|
|
+ public static ElasticsearchCluster cluster = ElasticsearchCluster.local().module("mapper-extras").module("wildcard").build();
|
|
|
+
|
|
|
+ // The dynamic templates we test against
|
|
|
+ public static final String ECS_DYNAMIC_TEMPLATES_FILE = "ecs-dynamic-mappings.json";
|
|
|
+
|
|
|
+ // The current ECS state (branch main) containing all fields in flattened form
|
|
|
+ private static final String ECS_FLAT_FILE_URL = "https://raw.githubusercontent.com/elastic/ecs/main/generated/ecs/ecs_flat.yml";
|
|
|
+
|
|
|
+ private static final Set<String> OMIT_FIELD_TYPES = Set.of("object", "nested");
|
|
|
+
|
|
|
+ private static final Set<String> OMIT_FIELDS = Set.of("data_stream.dataset", "data_stream.namespace", "data_stream.type");
|
|
|
+
|
|
|
+ private static Map<String, Object> ecsDynamicTemplates;
|
|
|
+ private static Map<String, Map<String, Object>> ecsFlatFieldDefinitions;
|
|
|
+ private static Map<String, String> ecsFlatMultiFieldDefinitions;
|
|
|
+
|
|
|
+ @BeforeClass
|
|
|
+ public static void setupSuiteScopeCluster() throws Exception {
|
|
|
+ prepareEcsDynamicTemplates();
|
|
|
+ prepareEcsDefinitions();
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void prepareEcsDynamicTemplates() throws IOException {
|
|
|
+ String rawEcsComponentTemplate = TemplateUtils.loadTemplate(
|
|
|
+ "/" + ECS_DYNAMIC_TEMPLATES_FILE,
|
|
|
+ Integer.toString(1),
|
|
|
+ StackTemplateRegistry.TEMPLATE_VERSION_VARIABLE,
|
|
|
+ Collections.emptyMap()
|
|
|
+ );
|
|
|
+ Map<String, Object> ecsDynamicTemplatesRaw;
|
|
|
+ try (
|
|
|
+ XContentParser parser = XContentFactory.xContent(XContentType.JSON)
|
|
|
+ .createParser(XContentParserConfiguration.EMPTY, rawEcsComponentTemplate)
|
|
|
+ ) {
|
|
|
+ ecsDynamicTemplatesRaw = parser.map();
|
|
|
+ }
|
|
|
+
|
|
|
+ String errorMessage = String.format(
|
|
|
+ Locale.ENGLISH,
|
|
|
+ "ECS mappings component template '%s' structure has changed, this test needs to be adjusted",
|
|
|
+ ECS_DYNAMIC_TEMPLATES_FILE
|
|
|
+ );
|
|
|
+ assertFalse(errorMessage, rawEcsComponentTemplate.isEmpty());
|
|
|
+ Object mappings = ecsDynamicTemplatesRaw.get("template");
|
|
|
+ assertNotNull(errorMessage, mappings);
|
|
|
+ assertThat(errorMessage, mappings, instanceOf(Map.class));
|
|
|
+ Object dynamicTemplates = ((Map<?, ?>) mappings).get("mappings");
|
|
|
+ assertNotNull(errorMessage, dynamicTemplates);
|
|
|
+ assertThat(errorMessage, dynamicTemplates, instanceOf(Map.class));
|
|
|
+ assertEquals(errorMessage, 1, ((Map<?, ?>) dynamicTemplates).size());
|
|
|
+ assertTrue(errorMessage, ((Map<?, ?>) dynamicTemplates).containsKey("dynamic_templates"));
|
|
|
+ ecsDynamicTemplates = (Map<String, Object>) dynamicTemplates;
|
|
|
+ }
|
|
|
+
|
|
|
+ @SuppressForbidden(reason = "Opening socket connection to read ECS definitions from ECS GitHub repo")
|
|
|
+ private static void prepareEcsDefinitions() throws IOException {
|
|
|
+ Map<String, ?> ecsFlatFieldsRawMap;
|
|
|
+ URL ecsDefinitionsFlatFileUrl = new URL(ECS_FLAT_FILE_URL);
|
|
|
+ try (InputStream ecsDynamicTemplatesIS = ecsDefinitionsFlatFileUrl.openStream()) {
|
|
|
+ try (
|
|
|
+ XContentParser parser = XContentFactory.xContent(XContentType.YAML)
|
|
|
+ .createParser(XContentParserConfiguration.EMPTY, ecsDynamicTemplatesIS)
|
|
|
+ ) {
|
|
|
+ ecsFlatFieldsRawMap = parser.map();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ String errorMessage = String.format(
|
|
|
+ Locale.ENGLISH,
|
|
|
+ "ECS flat mapping file at %s has changed, this test needs to be adjusted",
|
|
|
+ ECS_FLAT_FILE_URL
|
|
|
+ );
|
|
|
+ assertFalse(errorMessage, ecsFlatFieldsRawMap.isEmpty());
|
|
|
+ Map.Entry<String, ?> fieldEntry = ecsFlatFieldsRawMap.entrySet().iterator().next();
|
|
|
+ assertThat(errorMessage, fieldEntry.getValue(), instanceOf(Map.class));
|
|
|
+ Map<?, ?> fieldProperties = (Map<?, ?>) fieldEntry.getValue();
|
|
|
+ assertFalse(errorMessage, fieldProperties.isEmpty());
|
|
|
+ Map.Entry<?, ?> fieldProperty = fieldProperties.entrySet().iterator().next();
|
|
|
+ assertThat(errorMessage, fieldProperty.getKey(), instanceOf(String.class));
|
|
|
+
|
|
|
+ OMIT_FIELDS.forEach(ecsFlatFieldsRawMap::remove);
|
|
|
+
|
|
|
+ // noinspection
|
|
|
+ ecsFlatFieldDefinitions = (Map<String, Map<String, Object>>) ecsFlatFieldsRawMap;
|
|
|
+ ecsFlatMultiFieldDefinitions = new HashMap<>();
|
|
|
+ Iterator<Map.Entry<String, Map<String, Object>>> iterator = ecsFlatFieldDefinitions.entrySet().iterator();
|
|
|
+ while (iterator.hasNext()) {
|
|
|
+ Map.Entry<String, Map<String, Object>> entry = iterator.next();
|
|
|
+ Map<String, Object> definitions = entry.getValue();
|
|
|
+ String type = (String) definitions.get("type");
|
|
|
+ if (OMIT_FIELD_TYPES.contains(type)) {
|
|
|
+ iterator.remove();
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, String>> multiFields = (List<Map<String, String>>) definitions.get("multi_fields");
|
|
|
+ if (multiFields != null) {
|
|
|
+ multiFields.forEach(multiFieldsDefinitions -> {
|
|
|
+ String subfieldFlatName = Objects.requireNonNull(multiFieldsDefinitions.get("flat_name"));
|
|
|
+ String subfieldType = Objects.requireNonNull(multiFieldsDefinitions.get("type"));
|
|
|
+ ecsFlatMultiFieldDefinitions.put(subfieldFlatName, subfieldType);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ protected String getTestRestCluster() {
|
|
|
+ return cluster.getHttpAddresses();
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testFlattenedFields() throws IOException {
|
|
|
+ String indexName = "test-flattened-fields";
|
|
|
+ createTestIndex(indexName);
|
|
|
+ Map<String, Object> flattenedFieldsMap = createTestDocument(true);
|
|
|
+ indexDocument(indexName, flattenedFieldsMap);
|
|
|
+ verifyEcsMappings(indexName);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testFlattenedFieldsWithoutSubobjects() throws IOException {
|
|
|
+ String indexName = "test_flattened_fields_subobjects_false";
|
|
|
+ createTestIndex(indexName, Map.of("subobjects", false));
|
|
|
+ Map<String, Object> flattenedFieldsMap = createTestDocument(true);
|
|
|
+ indexDocument(indexName, flattenedFieldsMap);
|
|
|
+ verifyEcsMappings(indexName);
|
|
|
+ }
|
|
|
+
|
|
|
+ public void testNestedFields() throws IOException {
|
|
|
+ String indexName = "test-nested-fields";
|
|
|
+ createTestIndex(indexName);
|
|
|
+ Map<String, Object> nestedFieldsMap = createTestDocument(false);
|
|
|
+ indexDocument(indexName, nestedFieldsMap);
|
|
|
+ verifyEcsMappings(indexName);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void indexDocument(String indexName, Map<String, Object> flattenedFieldsMap) throws IOException {
|
|
|
+ try (XContentBuilder bodyBuilder = JsonXContent.contentBuilder()) {
|
|
|
+ Request indexRequest = new Request("POST", "/" + indexName + "/_doc");
|
|
|
+ indexRequest.setJsonEntity(Strings.toString(bodyBuilder.map(flattenedFieldsMap)));
|
|
|
+ // noinspection resource
|
|
|
+ Response response = ESRestTestCase.client().performRequest(indexRequest);
|
|
|
+ assertEquals(HttpStatus.SC_CREATED, response.getStatusLine().getStatusCode());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> createTestDocument(boolean flattened) {
|
|
|
+ Map<String, Object> testFieldsMap = new HashMap<>();
|
|
|
+ for (Map.Entry<String, Map<String, Object>> fieldEntry : ecsFlatFieldDefinitions.entrySet()) {
|
|
|
+ String flattenedFieldName = fieldEntry.getKey();
|
|
|
+ Map<String, Object> fieldDefinitions = fieldEntry.getValue();
|
|
|
+ String type = (String) fieldDefinitions.get("type");
|
|
|
+ assertNotNull(
|
|
|
+ String.format(Locale.ENGLISH, "Can't find type for field '%s' in %s file", flattenedFieldName, ECS_DYNAMIC_TEMPLATES_FILE),
|
|
|
+ type
|
|
|
+ );
|
|
|
+ Object testValue = generateTestValue(type);
|
|
|
+ if (flattened) {
|
|
|
+ testFieldsMap.put(flattenedFieldName, testValue);
|
|
|
+ } else {
|
|
|
+ Map<String, Object> currentField = testFieldsMap;
|
|
|
+ Iterator<String> fieldPathPartsIterator = Arrays.stream(flattenedFieldName.split("\\.")).iterator();
|
|
|
+ String subfield = fieldPathPartsIterator.next();
|
|
|
+ while (fieldPathPartsIterator.hasNext()) {
|
|
|
+ currentField = (Map<String, Object>) currentField.computeIfAbsent(subfield, ignore -> new HashMap<>());
|
|
|
+ subfield = fieldPathPartsIterator.next();
|
|
|
+ }
|
|
|
+ currentField.put(subfield, testValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return testFieldsMap;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void createTestIndex(String indexName) throws IOException {
|
|
|
+ createTestIndex(indexName, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void createTestIndex(String indexName, @Nullable Map<String, Object> customMappings) throws IOException {
|
|
|
+ final Map<String, Object> indexMappings;
|
|
|
+ if (customMappings != null) {
|
|
|
+ indexMappings = Stream.concat(ecsDynamicTemplates.entrySet().stream(), customMappings.entrySet().stream())
|
|
|
+ .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
|
|
|
+
|
|
|
+ } else {
|
|
|
+ indexMappings = ecsDynamicTemplates;
|
|
|
+ }
|
|
|
+ try (XContentBuilder bodyBuilder = JsonXContent.contentBuilder()) {
|
|
|
+ bodyBuilder.startObject();
|
|
|
+ bodyBuilder.startObject("settings");
|
|
|
+ bodyBuilder.field("index.mapping.total_fields.limit", 10000);
|
|
|
+ bodyBuilder.endObject();
|
|
|
+ bodyBuilder.field("mappings", indexMappings);
|
|
|
+ bodyBuilder.endObject();
|
|
|
+
|
|
|
+ Request createIndexRequest = new Request("PUT", "/" + indexName);
|
|
|
+ createIndexRequest.setJsonEntity(Strings.toString(bodyBuilder));
|
|
|
+ // noinspection resource
|
|
|
+ Response response = ESRestTestCase.client().performRequest(createIndexRequest);
|
|
|
+ assertEquals(HttpStatus.SC_OK, response.getStatusLine().getStatusCode());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private Object generateTestValue(String type) {
|
|
|
+ switch (type) {
|
|
|
+ case "geo_point" -> {
|
|
|
+ return new double[] { randomDouble(), randomDouble() };
|
|
|
+ }
|
|
|
+ case "long" -> {
|
|
|
+ return randomLong();
|
|
|
+ }
|
|
|
+ case "int" -> {
|
|
|
+ return randomInt();
|
|
|
+ }
|
|
|
+ case "float", "scaled_float" -> {
|
|
|
+ return randomFloat();
|
|
|
+ }
|
|
|
+ case "keyword", "wildcard", "text", "match_only_text" -> {
|
|
|
+ return randomAlphaOfLength(20);
|
|
|
+ }
|
|
|
+ case "constant_keyword" -> {
|
|
|
+ return "test";
|
|
|
+ }
|
|
|
+ case "date" -> {
|
|
|
+ return DateFormatter.forPattern("strict_date_optional_time").formatMillis(System.currentTimeMillis());
|
|
|
+ }
|
|
|
+ case "ip" -> {
|
|
|
+ return NetworkAddress.format(randomIp(true));
|
|
|
+ }
|
|
|
+ case "boolean" -> {
|
|
|
+ return randomBoolean();
|
|
|
+ }
|
|
|
+ case "flattened" -> {
|
|
|
+ // creating multiple subfields
|
|
|
+ return Map.of("subfield1", randomAlphaOfLength(20), "subfield2", randomAlphaOfLength(20));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ throw new IllegalArgumentException("Unknown field type: " + type);
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<String, Object> getMappings(String indexName) throws IOException {
|
|
|
+ Request getMappingRequest = new Request("GET", "/" + indexName + "/_mapping");
|
|
|
+ // noinspection resource
|
|
|
+ Response response = ESRestTestCase.client().performRequest(getMappingRequest);
|
|
|
+ assertEquals(response.getStatusLine().getStatusCode(), HttpStatus.SC_OK);
|
|
|
+ Map<String, Object> mappingResponse;
|
|
|
+ try (XContentParser parser = createParser(JsonXContent.jsonXContent, response.getEntity().getContent())) {
|
|
|
+ mappingResponse = parser.map();
|
|
|
+ }
|
|
|
+ assertThat(mappingResponse.size(), equalTo(1));
|
|
|
+ Map<String, Object> indexMap = (Map<String, Object>) mappingResponse.get(indexName);
|
|
|
+ assertNotNull(indexMap);
|
|
|
+ Map<String, Object> mappings = (Map<String, Object>) indexMap.get("mappings");
|
|
|
+ assertNotNull(mappings);
|
|
|
+ return (Map<String, Object>) mappings.get("properties");
|
|
|
+ }
|
|
|
+
|
|
|
+ private void processRawMappingsSubtree(
|
|
|
+ final Map<String, Object> fieldSubtrees,
|
|
|
+ final Map<String, String> flatFieldMappings,
|
|
|
+ final Map<String, String> flatMultiFieldsMappings,
|
|
|
+ final String subtreePrefix
|
|
|
+ ) {
|
|
|
+ fieldSubtrees.forEach((fieldName, fieldMappings) -> {
|
|
|
+ String fieldFullPath = subtreePrefix + fieldName;
|
|
|
+ Map<String, Object> fieldMappingsMap = ((Map<String, Object>) fieldMappings);
|
|
|
+ String type = (String) fieldMappingsMap.get("type");
|
|
|
+ if (type != null) {
|
|
|
+ flatFieldMappings.put(fieldFullPath, type);
|
|
|
+ }
|
|
|
+ Map<String, Object> subfields = (Map<String, Object>) fieldMappingsMap.get("properties");
|
|
|
+ if (subfields != null) {
|
|
|
+ processRawMappingsSubtree(subfields, flatFieldMappings, flatMultiFieldsMappings, fieldFullPath + ".");
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Map<String, String>> fields = (Map<String, Map<String, String>>) fieldMappingsMap.get("fields");
|
|
|
+ if (fields != null) {
|
|
|
+ fields.forEach((subFieldName, multiFieldMappings) -> {
|
|
|
+ String subFieldFullPath = fieldFullPath + "." + subFieldName;
|
|
|
+ String subFieldType = Objects.requireNonNull(multiFieldMappings.get("type"));
|
|
|
+ flatMultiFieldsMappings.put(subFieldFullPath, subFieldType);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+
|
|
|
+ private void verifyEcsMappings(String indexName) throws IOException {
|
|
|
+ final Map<String, Object> rawMappings = getMappings(indexName);
|
|
|
+ final Map<String, String> flatFieldMappings = new HashMap<>();
|
|
|
+ final Map<String, String> flatMultiFieldsMappings = new HashMap<>();
|
|
|
+ processRawMappingsSubtree(rawMappings, flatFieldMappings, flatMultiFieldsMappings, "");
|
|
|
+
|
|
|
+ Map<String, Map<String, Object>> shallowFieldMapCopy = new HashMap<>(ecsFlatFieldDefinitions);
|
|
|
+ logger.info("Testing mapping of {} ECS fields", shallowFieldMapCopy.size());
|
|
|
+ List<String> nonEcsFields = new ArrayList<>();
|
|
|
+ Map<String, String> fieldToWrongMappingType = new HashMap<>();
|
|
|
+ flatFieldMappings.forEach((fieldName, actualMappingType) -> {
|
|
|
+ Map<String, Object> expectedMappings = shallowFieldMapCopy.remove(fieldName);
|
|
|
+ if (expectedMappings == null) {
|
|
|
+ nonEcsFields.add(fieldName);
|
|
|
+ } else {
|
|
|
+ String expectedType = (String) expectedMappings.get("type");
|
|
|
+ if (actualMappingType.equals(expectedType) == false) {
|
|
|
+ fieldToWrongMappingType.put(fieldName, actualMappingType);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ Map<String, String> shallowMultiFieldMapCopy = new HashMap<>(ecsFlatMultiFieldDefinitions);
|
|
|
+ logger.info("Testing mapping of {} ECS multi-fields", shallowMultiFieldMapCopy.size());
|
|
|
+ flatMultiFieldsMappings.forEach((fieldName, actualMappingType) -> {
|
|
|
+ String expectedType = shallowMultiFieldMapCopy.remove(fieldName);
|
|
|
+ if (expectedType != null) {
|
|
|
+ // not finding an entry in the expected multi-field mappings map is acceptable: our dynamic templates are required to
|
|
|
+ // ensure multi-field mapping for all fields with such ECS definitions. However, the patterns in these templates may lead
|
|
|
+ // to multi-field mapping for ECS fields for which such are not defined
|
|
|
+ if (actualMappingType.equals(expectedType) == false) {
|
|
|
+ fieldToWrongMappingType.put(fieldName, actualMappingType);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ shallowFieldMapCopy.forEach(
|
|
|
+ (fieldName, expectedMappings) -> logger.error(
|
|
|
+ "ECS field '{}' is not covered by the current dynamic templates. Update {} so that this field is mapped to type '{}'.",
|
|
|
+ fieldName,
|
|
|
+ ECS_DYNAMIC_TEMPLATES_FILE,
|
|
|
+ expectedMappings.get("type")
|
|
|
+ )
|
|
|
+ );
|
|
|
+ shallowMultiFieldMapCopy.keySet().forEach(field -> {
|
|
|
+ int lastDotIndex = field.lastIndexOf('.');
|
|
|
+ String parentField = field.substring(0, lastDotIndex);
|
|
|
+ String subfield = field.substring(lastDotIndex + 1);
|
|
|
+ logger.error(
|
|
|
+ "ECS field '{}' is expected to have a multi-field mapping with subfield '{}'. Fix {} accordingly.",
|
|
|
+ parentField,
|
|
|
+ subfield,
|
|
|
+ ECS_DYNAMIC_TEMPLATES_FILE
|
|
|
+ );
|
|
|
+ });
|
|
|
+ fieldToWrongMappingType.forEach((fieldName, actualMappingType) -> {
|
|
|
+ String ecsExpectedType = (String) ecsFlatFieldDefinitions.get(fieldName).get("type");
|
|
|
+ logger.error(
|
|
|
+ "ECS field '{}' should be mapped to type '{}' but is mapped to type '{}'. Update {} accordingly.",
|
|
|
+ fieldName,
|
|
|
+ ecsExpectedType,
|
|
|
+ actualMappingType,
|
|
|
+ ECS_DYNAMIC_TEMPLATES_FILE
|
|
|
+ );
|
|
|
+ });
|
|
|
+ nonEcsFields.forEach(field -> logger.error("The test document contains '{}', which is not an ECS field", field));
|
|
|
+
|
|
|
+ assertTrue("ECS is not fully covered by the current ECS dynamic templates, see details above", shallowFieldMapCopy.isEmpty());
|
|
|
+ assertTrue(
|
|
|
+ "ECS is not fully covered by the current ECS dynamic templates' multi-fields definitions, see details above",
|
|
|
+ shallowMultiFieldMapCopy.isEmpty()
|
|
|
+ );
|
|
|
+ assertTrue(
|
|
|
+ "At least one field was mapped with a type that mismatches the ECS definitions, see details above",
|
|
|
+ fieldToWrongMappingType.isEmpty()
|
|
|
+ );
|
|
|
+ assertTrue("The test document contains non-ECS fields, see details above", nonEcsFields.isEmpty());
|
|
|
+ }
|
|
|
+}
|