Prevent constructing index template without patterns (#27662)

Today, we prevent the system from storing a broken index template in the
transport layer, however we don't prevent this in XContent. A broken
index template can break the whole cluster state.

This commit attempts to prevent the system from constructing an index
template without a proper index patterns.
This commit is contained in:
Nhat Nguyen 2017-12-05 15:38:33 -05:00 committed by GitHub
parent 2f9a882061
commit ed2caf2bad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 80 additions and 19 deletions

View File

@ -93,6 +93,9 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
ImmutableOpenMap<String, CompressedXContent> mappings, ImmutableOpenMap<String, CompressedXContent> mappings,
ImmutableOpenMap<String, AliasMetaData> aliases, ImmutableOpenMap<String, AliasMetaData> aliases,
ImmutableOpenMap<String, IndexMetaData.Custom> customs) { ImmutableOpenMap<String, IndexMetaData.Custom> customs) {
if (patterns == null || patterns.isEmpty()) {
throw new IllegalArgumentException("Index patterns must not be null or empty; got " + patterns);
}
this.name = name; this.name = name;
this.order = order; this.order = order;
this.version = version; this.version = version;
@ -244,7 +247,7 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
if (out.getVersion().onOrAfter(Version.V_6_0_0_alpha1)) { if (out.getVersion().onOrAfter(Version.V_6_0_0_alpha1)) {
out.writeStringList(patterns); out.writeStringList(patterns);
} else { } else {
out.writeString(patterns.size() > 0 ? patterns.get(0) : ""); out.writeString(patterns.get(0));
} }
Settings.writeSettingsToStream(settings, out); Settings.writeSettingsToStream(settings, out);
out.writeVInt(mappings.size()); out.writeVInt(mappings.size());

View File

@ -21,6 +21,7 @@ package org.elasticsearch.cluster.metadata;
import org.elasticsearch.Version; import org.elasticsearch.Version;
import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
@ -120,4 +121,45 @@ public class IndexTemplateMetaDataTests extends ESTestCase {
assertThat(indexTemplateMetaData, equalTo(indexTemplateMetaDataRoundTrip)); assertThat(indexTemplateMetaData, equalTo(indexTemplateMetaDataRoundTrip));
} }
public void testValidateInvalidIndexPatterns() throws Exception {
final IllegalArgumentException emptyPatternError = expectThrows(IllegalArgumentException.class, () -> {
new IndexTemplateMetaData(randomRealisticUnicodeOfLengthBetween(5, 10), randomInt(), randomInt(),
Collections.emptyList(), Settings.EMPTY, ImmutableOpenMap.of(), ImmutableOpenMap.of(), ImmutableOpenMap.of());
});
assertThat(emptyPatternError.getMessage(), equalTo("Index patterns must not be null or empty; got []"));
final IllegalArgumentException nullPatternError = expectThrows(IllegalArgumentException.class, () -> {
new IndexTemplateMetaData(randomRealisticUnicodeOfLengthBetween(5, 10), randomInt(), randomInt(),
null, Settings.EMPTY, ImmutableOpenMap.of(), ImmutableOpenMap.of(), ImmutableOpenMap.of());
});
assertThat(nullPatternError.getMessage(), equalTo("Index patterns must not be null or empty; got null"));
final String templateWithEmptyPattern = "{\"index_patterns\" : [],\"order\" : 1000," +
"\"settings\" : {\"number_of_shards\" : 10,\"number_of_replicas\" : 1}," +
"\"mappings\" : {\"doc\" :" +
"{\"properties\":{\"" +
randomAlphaOfLength(10) + "\":{\"type\":\"text\"},\"" +
randomAlphaOfLength(10) + "\":{\"type\":\"keyword\"}}" +
"}}}";
try (XContentParser parser =
XContentHelper.createParser(NamedXContentRegistry.EMPTY, new BytesArray(templateWithEmptyPattern), XContentType.JSON)) {
final IllegalArgumentException ex = expectThrows(IllegalArgumentException.class,
() -> IndexTemplateMetaData.Builder.fromXContent(parser, randomAlphaOfLengthBetween(1, 100)));
assertThat(ex.getMessage(), equalTo("Index patterns must not be null or empty; got []"));
}
final String templateWithoutPattern = "{\"order\" : 1000," +
"\"settings\" : {\"number_of_shards\" : 10,\"number_of_replicas\" : 1}," +
"\"mappings\" : {\"doc\" :" +
"{\"properties\":{\"" +
randomAlphaOfLength(10) + "\":{\"type\":\"text\"},\"" +
randomAlphaOfLength(10) + "\":{\"type\":\"keyword\"}}" +
"}}}";
try (XContentParser parser =
XContentHelper.createParser(NamedXContentRegistry.EMPTY, new BytesArray(templateWithoutPattern), XContentType.JSON)) {
final IllegalArgumentException ex = expectThrows(IllegalArgumentException.class,
() -> IndexTemplateMetaData.Builder.fromXContent(parser, randomAlphaOfLengthBetween(1, 100)));
assertThat(ex.getMessage(), equalTo("Index patterns must not be null or empty; got null"));
}
}
} }

View File

@ -54,6 +54,8 @@ import java.util.Set;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static org.elasticsearch.test.VersionUtils.randomVersion; import static org.elasticsearch.test.VersionUtils.randomVersion;
@ -61,9 +63,7 @@ import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.CoreMatchers.startsWith;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.greaterThanOrEqualTo;
import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.lessThan;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doAnswer;
@ -83,16 +83,17 @@ public class TemplateUpgradeServiceTests extends ESTestCase {
boolean shouldChange = randomBoolean(); boolean shouldChange = randomBoolean();
MetaData metaData = randomMetaData( MetaData metaData = randomMetaData(
IndexTemplateMetaData.builder("user_template").build(), IndexTemplateMetaData.builder("user_template").patterns(randomIndexPatterns()).build(),
IndexTemplateMetaData.builder("removed_test_template").build(), IndexTemplateMetaData.builder("removed_test_template").patterns(randomIndexPatterns()).build(),
IndexTemplateMetaData.builder("changed_test_template").build() IndexTemplateMetaData.builder("changed_test_template").patterns(randomIndexPatterns()).build()
); );
TemplateUpgradeService service = new TemplateUpgradeService(Settings.EMPTY, null, clusterService, null, TemplateUpgradeService service = new TemplateUpgradeService(Settings.EMPTY, null, clusterService, null,
Arrays.asList( Arrays.asList(
templates -> { templates -> {
if (shouldAdd) { if (shouldAdd) {
assertNull(templates.put("added_test_template", IndexTemplateMetaData.builder("added_test_template").build())); assertNull(templates.put("added_test_template",
IndexTemplateMetaData.builder("added_test_template").patterns(randomIndexPatterns()).build()));
} }
return templates; return templates;
}, },
@ -105,7 +106,7 @@ public class TemplateUpgradeServiceTests extends ESTestCase {
templates -> { templates -> {
if (shouldChange) { if (shouldChange) {
assertNotNull(templates.put("changed_test_template", assertNotNull(templates.put("changed_test_template",
IndexTemplateMetaData.builder("changed_test_template").order(10).build())); IndexTemplateMetaData.builder("changed_test_template").patterns(randomIndexPatterns()).order(10).build()));
} }
return templates; return templates;
} }
@ -234,9 +235,9 @@ public class TemplateUpgradeServiceTests extends ESTestCase {
AtomicInteger updateInvocation = new AtomicInteger(); AtomicInteger updateInvocation = new AtomicInteger();
MetaData metaData = randomMetaData( MetaData metaData = randomMetaData(
IndexTemplateMetaData.builder("user_template").build(), IndexTemplateMetaData.builder("user_template").patterns(randomIndexPatterns()).build(),
IndexTemplateMetaData.builder("removed_test_template").build(), IndexTemplateMetaData.builder("removed_test_template").patterns(randomIndexPatterns()).build(),
IndexTemplateMetaData.builder("changed_test_template").build() IndexTemplateMetaData.builder("changed_test_template").patterns(randomIndexPatterns()).build()
); );
ThreadPool threadPool = mock(ThreadPool.class); ThreadPool threadPool = mock(ThreadPool.class);
@ -390,4 +391,10 @@ public class TemplateUpgradeServiceTests extends ESTestCase {
} }
return builder.build(); return builder.build();
} }
List<String> randomIndexPatterns() {
return IntStream.range(0, between(1, 10))
.mapToObj(n -> randomUnicodeOfCodepointLengthBetween(1, 100))
.collect(Collectors.toList());
}
} }

View File

@ -46,6 +46,7 @@ import org.elasticsearch.test.VersionUtils;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.equalTo;
@ -170,8 +171,8 @@ public class ClusterSerializationTests extends ESAllocationTestCase {
public void testObjectReuseWhenApplyingClusterStateDiff() throws Exception { public void testObjectReuseWhenApplyingClusterStateDiff() throws Exception {
IndexMetaData indexMetaData IndexMetaData indexMetaData
= IndexMetaData.builder("test").settings(settings(Version.CURRENT)).numberOfShards(10).numberOfReplicas(1).build(); = IndexMetaData.builder("test").settings(settings(Version.CURRENT)).numberOfShards(10).numberOfReplicas(1).build();
IndexTemplateMetaData indexTemplateMetaData IndexTemplateMetaData indexTemplateMetaData = IndexTemplateMetaData.builder("test-template")
= IndexTemplateMetaData.builder("test-template").patterns(new ArrayList<>()).build(); .patterns(Arrays.asList(generateRandomStringArray(10, 100, false, false))).build();
MetaData metaData = MetaData.builder().put(indexMetaData, true).put(indexTemplateMetaData).build(); MetaData metaData = MetaData.builder().put(indexMetaData, true).put(indexTemplateMetaData).build();
RoutingTable routingTable = RoutingTable.builder().addAsNew(metaData.index("test")).build(); RoutingTable routingTable = RoutingTable.builder().addAsNew(metaData.index("test")).build();

View File

@ -32,6 +32,8 @@ import org.elasticsearch.cluster.routing.allocation.AllocationService;
import org.elasticsearch.common.Strings; import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import java.util.Arrays;
import static java.util.Collections.emptyMap; import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet; import static java.util.Collections.emptySet;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.containsString;
@ -40,7 +42,8 @@ public class ClusterStateToStringTests extends ESAllocationTestCase {
public void testClusterStateSerialization() throws Exception { public void testClusterStateSerialization() throws Exception {
MetaData metaData = MetaData.builder() MetaData metaData = MetaData.builder()
.put(IndexMetaData.builder("test_idx").settings(settings(Version.CURRENT)).numberOfShards(10).numberOfReplicas(1)) .put(IndexMetaData.builder("test_idx").settings(settings(Version.CURRENT)).numberOfShards(10).numberOfReplicas(1))
.put(IndexTemplateMetaData.builder("test_template").build()) .put(IndexTemplateMetaData.builder("test_template")
.patterns(Arrays.asList(generateRandomStringArray(10, 100, false,false))).build())
.build(); .build();
RoutingTable routingTable = RoutingTable.builder() RoutingTable routingTable = RoutingTable.builder()

View File

@ -308,7 +308,8 @@ public class GatewayMetaStateTests extends ESAllocationTestCase {
Collections.emptyList(), Collections.emptyList(),
Collections.singletonList( Collections.singletonList(
templates -> { templates -> {
templates.put("added_test_template", IndexTemplateMetaData.builder("added_test_template").build()); templates.put("added_test_template", IndexTemplateMetaData.builder("added_test_template")
.patterns(Arrays.asList(generateRandomStringArray(10, 100, false, false))).build());
return templates; return templates;
} }
)); ));
@ -438,14 +439,17 @@ public class GatewayMetaStateTests extends ESAllocationTestCase {
Collections.emptyList(), Collections.emptyList(),
Arrays.asList( Arrays.asList(
indexTemplateMetaDatas -> { indexTemplateMetaDatas -> {
indexTemplateMetaDatas.put("template1", IndexTemplateMetaData.builder("template1").settings( indexTemplateMetaDatas.put("template1", IndexTemplateMetaData.builder("template1")
Settings.builder().put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 20).build()).build()); .patterns(Arrays.asList(generateRandomStringArray(10, 100, false, false)))
.settings(Settings.builder().put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), 20).build())
.build());
return indexTemplateMetaDatas; return indexTemplateMetaDatas;
}, },
indexTemplateMetaDatas -> { indexTemplateMetaDatas -> {
indexTemplateMetaDatas.put("template2", IndexTemplateMetaData.builder("template2").settings( indexTemplateMetaDatas.put("template2", IndexTemplateMetaData.builder("template2")
Settings.builder().put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 10).build()).build()); .patterns(Arrays.asList(generateRandomStringArray(10, 100, false, false)))
.settings(Settings.builder().put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), 10).build()).build());
return indexTemplateMetaDatas; return indexTemplateMetaDatas;
} }
@ -535,6 +539,7 @@ public class GatewayMetaStateTests extends ESAllocationTestCase {
.settings(settings(Version.CURRENT) .settings(settings(Version.CURRENT)
.put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), randomIntBetween(0, 3)) .put(IndexMetaData.INDEX_NUMBER_OF_REPLICAS_SETTING.getKey(), randomIntBetween(0, 3))
.put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), randomIntBetween(1, 5))) .put(IndexMetaData.INDEX_NUMBER_OF_SHARDS_SETTING.getKey(), randomIntBetween(1, 5)))
.patterns(Arrays.asList(generateRandomStringArray(10, 100, false, false)))
.build(); .build();
builder.put(templateMetaData); builder.put(templateMetaData);
} }