Fix template equals when mappings are wrapped (#77008)

When create template v2. mappings will add _doc. This will cause the created template to be inconsistent with the queried template.

In template class, add mappingsEquals method, to deal with this case.

reproduced:
MetadataIndexTemplateServiceTests.testAddComponentTemplate
when mappings are not null, the case will failed.

```
        Template template = new Template(
            Settings.builder().build(),
            new CompressedXContent("{\"properties\":{\"@timestamp\":{\"type\":\"date\"}}}"),
            ComponentTemplateTests.randomAliases()
        );
        ComponentTemplate componentTemplate = new ComponentTemplate(template, 1L, new HashMap<>());
```

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
weizijun 2021-09-04 04:22:56 +08:00 committed by GitHub
parent 096b8ccc26
commit e321fb024e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 87 additions and 55 deletions

View File

@ -9,6 +9,7 @@
package org.elasticsearch.cluster.metadata;
import org.elasticsearch.cluster.AbstractDiffable;
import org.elasticsearch.common.util.Maps;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.common.xcontent.ParseField;
import org.elasticsearch.common.Strings;
@ -141,7 +142,7 @@ public class Template extends AbstractDiffable<Template> implements ToXContentOb
}
Template other = (Template) obj;
return Objects.equals(settings, other.settings) &&
Objects.equals(mappings, other.mappings) &&
mappingsEquals(this.mappings, other.mappings) &&
Objects.equals(aliases, other.aliases);
}
@ -178,11 +179,33 @@ public class Template extends AbstractDiffable<Template> implements ToXContentOb
}
@SuppressWarnings("unchecked")
private static Map<String, Object> reduceMapping(Map<String, Object> mapping) {
static Map<String, Object> reduceMapping(Map<String, Object> mapping) {
if (mapping.size() == 1 && MapperService.SINGLE_MAPPING_NAME.equals(mapping.keySet().iterator().next())) {
return (Map<String, Object>) mapping.values().iterator().next();
} else {
return mapping;
}
}
static boolean mappingsEquals(CompressedXContent m1, CompressedXContent m2) {
if (m1 == m2) {
return true;
}
if (m1 == null || m2 == null) {
return false;
}
if (m1.equals(m2)) {
return true;
}
Map<String, Object> thisUncompressedMapping = reduceMapping(
XContentHelper.convertToMap(m1.uncompressed(), true, XContentType.JSON).v2()
);
Map<String, Object> otherUncompressedMapping = reduceMapping(
XContentHelper.convertToMap(m2.uncompressed(), true, XContentType.JSON).v2()
);
return Maps.deepEquals(thisUncompressedMapping, otherUncompressedMapping);
}
}

View File

@ -9,10 +9,16 @@
package org.elasticsearch.cluster.metadata;
import org.elasticsearch.cluster.Diff;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.io.stream.Writeable;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentHelper;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.test.AbstractDiffableSerializationTestCase;
import org.elasticsearch.test.ESTestCase;
@ -20,6 +26,8 @@ import java.io.IOException;
import java.util.Collections;
import java.util.Map;
import static org.hamcrest.Matchers.equalTo;
public class ComponentTemplateTests extends AbstractDiffableSerializationTestCase<ComponentTemplate> {
@Override
protected ComponentTemplate makeTestChanges(ComponentTemplate testInstance) {
@ -154,4 +162,52 @@ public class ComponentTemplateTests extends AbstractDiffableSerializationTestCas
throw new IllegalStateException("illegal randomization branch");
}
}
public void testMappingsEquals() throws IOException {
{
CompressedXContent mappings = randomMappings();
assertThat(Template.mappingsEquals(mappings, mappings), equalTo(true));
}
{
assertThat(Template.mappingsEquals(null, null), equalTo(true));
}
{
CompressedXContent mappings = randomMappings();
assertThat(Template.mappingsEquals(mappings, null), equalTo(false));
assertThat(Template.mappingsEquals(null, mappings), equalTo(false));
}
{
String randomString = randomAlphaOfLength(10);
CompressedXContent m1 = new CompressedXContent("{\"properties\":{\"" + randomString + "\":{\"type\":\"keyword\"}}}");
CompressedXContent m2 = new CompressedXContent("{\"properties\":{\"" + randomString + "\":{\"type\":\"keyword\"}}}");
assertThat(Template.mappingsEquals(m1, m2), equalTo(true));
}
{
CompressedXContent m1 = randomMappings();
CompressedXContent m2 = new CompressedXContent("{\"properties\":{\"" + randomAlphaOfLength(10) + "\":{\"type\":\"keyword\"}}}");
assertThat(Template.mappingsEquals(m1, m2), equalTo(false));
}
{
Map<String, Object> map = XContentHelper.convertToMap(
new BytesArray(
"{\""
+ MapperService.SINGLE_MAPPING_NAME
+ "\":{\"properties\":{\""
+ randomAlphaOfLength(10)
+ "\":{\"type\":\"keyword\"}}}}"
),
true,
XContentType.JSON
).v2();
Map<String, Object> reduceMap = Template.reduceMapping(map);
CompressedXContent m1 = new CompressedXContent(Strings.toString(XContentFactory.jsonBuilder().map(map)));
CompressedXContent m2 = new CompressedXContent(Strings.toString(XContentFactory.jsonBuilder().map(reduceMap)));
assertThat(Template.mappingsEquals(m1, m2), equalTo(true));
}
}
}

View File

@ -23,11 +23,8 @@ import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.settings.IndexScopedSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.common.xcontent.LoggingDeprecationHandler;
import org.elasticsearch.common.xcontent.NamedXContentRegistry;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.env.Environment;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.mapper.DataStreamTimestampFieldMapper;
@ -42,7 +39,6 @@ import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.test.ESSingleNodeTestCase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
@ -341,7 +337,11 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
public void testAddComponentTemplate() throws Exception{
MetadataIndexTemplateService metadataIndexTemplateService = getMetadataIndexTemplateService();
ClusterState state = ClusterState.EMPTY_STATE;
Template template = new Template(Settings.builder().build(), null, ComponentTemplateTests.randomAliases());
Template template = new Template(
Settings.builder().build(),
new CompressedXContent("{\"properties\":{\"@timestamp\":{\"type\":\"date\"}}}"),
ComponentTemplateTests.randomAliases()
);
ComponentTemplate componentTemplate = new ComponentTemplate(template, 1L, new HashMap<>());
state = metadataIndexTemplateService.addComponentTemplate(state, false, "foo", componentTemplate);
@ -1587,55 +1587,8 @@ public class MetadataIndexTemplateServiceTests extends ESSingleNodeTestCase {
new IndexScopedSettings(Settings.EMPTY, IndexScopedSettings.BUILT_IN_INDEX_SETTINGS), xContentRegistry());
}
@SuppressWarnings("unchecked")
public static void assertTemplatesEqual(ComposableIndexTemplate actual, ComposableIndexTemplate expected) {
ComposableIndexTemplate actualNoTemplate = new ComposableIndexTemplate(actual.indexPatterns(), null,
actual.composedOf(), actual.priority(), actual.version(), actual.metadata(), actual.getDataStreamTemplate(), null);
ComposableIndexTemplate expectedNoTemplate = new ComposableIndexTemplate(expected.indexPatterns(), null,
expected.composedOf(), expected.priority(), expected.version(), expected.metadata(), expected.getDataStreamTemplate(), null);
assertThat(actualNoTemplate, equalTo(expectedNoTemplate));
Template actualTemplate = actual.template();
Template expectedTemplate = expected.template();
assertThat("expected both templates to have either a template or no template",
Objects.nonNull(actualTemplate), equalTo(Objects.nonNull(expectedTemplate)));
if (actualTemplate != null) {
assertThat(actualTemplate.settings(), equalTo(expectedTemplate.settings()));
assertThat(actualTemplate.aliases(), equalTo(expectedTemplate.aliases()));
assertThat("expected both templates to have either mappings or no mappings",
Objects.nonNull(actualTemplate.mappings()), equalTo(Objects.nonNull(expectedTemplate.mappings())));
if (actualTemplate.mappings() != null) {
Map<String, Object> actualMappings;
Map<String, Object> expectedMappings;
try (XContentParser parser = XContentType.JSON.xContent()
.createParser(new NamedXContentRegistry(List.of()), LoggingDeprecationHandler.INSTANCE,
actualTemplate.mappings().string())) {
actualMappings = parser.map();
} catch (IOException e) {
throw new AssertionError(e);
}
try (XContentParser parser = XContentType.JSON.xContent()
.createParser(new NamedXContentRegistry(List.of()), LoggingDeprecationHandler.INSTANCE,
expectedTemplate.mappings().string())) {
expectedMappings = parser.map();
} catch (IOException e) {
throw new AssertionError(e);
}
if (actualMappings.size() == 1 && actualMappings.containsKey(MapperService.SINGLE_MAPPING_NAME)) {
actualMappings = (Map<String, Object>) actualMappings.get(MapperService.SINGLE_MAPPING_NAME);
}
if (expectedMappings.size() == 1 && expectedMappings.containsKey(MapperService.SINGLE_MAPPING_NAME)) {
expectedMappings = (Map<String, Object>) expectedMappings.get(MapperService.SINGLE_MAPPING_NAME);
}
assertThat(actualMappings, equalTo(expectedMappings));
}
}
assertTrue(Objects.equals(actual, expected));
}
// Composable index template with data_stream definition need _timestamp meta field mapper,