From c3dee628c71059c7be3cf7e0f370dcb5333b5a06 Mon Sep 17 00:00:00 2001 From: Lee Hinman Date: Thu, 19 Mar 2020 15:04:00 -0600 Subject: [PATCH] [7.x] Add IndexTemplateV2 to MetaData (#53753) (#53827) * Add IndexTemplateV2 to MetaData (#53753) * Add IndexTemplateV2 to MetaData This adds the `IndexTemplateV2` and `IndexTemplateV2Metadata` class to be used for the new implementation of index templates. The new metadata is stored as a `MetaData.Custom` implementation. Relates to #53101 * Add ITV2Metadata unit tests Co-authored-by: Elastic Machine * Update min supported version constant Co-authored-by: Elastic Machine --- .../elasticsearch/cluster/ClusterModule.java | 5 + .../cluster/metadata/IndexTemplateV2.java | 201 ++++++++++++++++++ .../metadata/IndexTemplateV2Metadata.java | 170 +++++++++++++++ .../cluster/metadata/MetaData.java | 34 +++ .../IndexTemplateV2MetadataTests.java | 58 +++++ .../metadata/IndexTemplateV2Tests.java | 188 ++++++++++++++++ .../cluster/metadata/MetaDataTests.java | 1 + .../metadata/ToAndFromJsonMetaDataTests.java | 22 ++ 8 files changed, 679 insertions(+) create mode 100644 server/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateV2.java create mode 100644 server/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateV2Metadata.java create mode 100644 server/src/test/java/org/elasticsearch/cluster/metadata/IndexTemplateV2MetadataTests.java create mode 100644 server/src/test/java/org/elasticsearch/cluster/metadata/IndexTemplateV2Tests.java diff --git a/server/src/main/java/org/elasticsearch/cluster/ClusterModule.java b/server/src/main/java/org/elasticsearch/cluster/ClusterModule.java index 1f134e5d9e6..c78aa8a3fe6 100644 --- a/server/src/main/java/org/elasticsearch/cluster/ClusterModule.java +++ b/server/src/main/java/org/elasticsearch/cluster/ClusterModule.java @@ -25,6 +25,7 @@ import org.elasticsearch.cluster.action.shard.ShardStateAction; import org.elasticsearch.cluster.metadata.ComponentTemplateMetadata; import org.elasticsearch.cluster.metadata.IndexGraveyard; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.metadata.IndexTemplateV2Metadata; import org.elasticsearch.cluster.metadata.MetaData; import org.elasticsearch.cluster.metadata.MetaDataDeleteIndexService; import org.elasticsearch.cluster.metadata.MetaDataIndexAliasesService; @@ -133,6 +134,8 @@ public class ClusterModule extends AbstractModule { PersistentTasksCustomMetaData::readDiffFrom); registerMetaDataCustom(entries, ComponentTemplateMetadata.TYPE, ComponentTemplateMetadata::new, ComponentTemplateMetadata::readDiffFrom); + registerMetaDataCustom(entries, IndexTemplateV2Metadata.TYPE, IndexTemplateV2Metadata::new, + IndexTemplateV2Metadata::readDiffFrom); // Task Status (not Diffable) entries.add(new Entry(Task.Status.class, PersistentTasksNodeService.Status.NAME, PersistentTasksNodeService.Status::new)); return entries; @@ -182,6 +185,8 @@ public class ClusterModule extends AbstractModule { PersistentTasksCustomMetaData::fromXContent)); entries.add(new NamedXContentRegistry.Entry(MetaData.Custom.class, new ParseField(ComponentTemplateMetadata.TYPE), ComponentTemplateMetadata::fromXContent)); + entries.add(new NamedXContentRegistry.Entry(MetaData.Custom.class, new ParseField(IndexTemplateV2Metadata.TYPE), + IndexTemplateV2Metadata::fromXContent)); return entries; } diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateV2.java b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateV2.java new file mode 100644 index 00000000000..e707b844881 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateV2.java @@ -0,0 +1,201 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.cluster.metadata; + +import org.elasticsearch.cluster.AbstractDiffable; +import org.elasticsearch.cluster.Diff; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.ToXContentObject; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +/** + * An index template is comprised of a set of index patterns, an optional template, and a list of + * ids corresponding to component templates that should be composed in order when creating a new + * index. + */ +public class IndexTemplateV2 extends AbstractDiffable implements ToXContentObject { + private static final ParseField INDEX_PATTERNS = new ParseField("index_patterns"); + private static final ParseField TEMPLATE = new ParseField("template"); + private static final ParseField PRIORITY = new ParseField("priority"); + private static final ParseField COMPOSED_OF = new ParseField("composed_of"); + private static final ParseField VERSION = new ParseField("version"); + private static final ParseField METADATA = new ParseField("_meta"); + + @SuppressWarnings("unchecked") + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>("index_template", false, + a -> new IndexTemplateV2((List) a[0], + (Template) a[1], + (List) a[2], + (Long) a[3], + (Long) a[4], + (Map) a[5])); + + static { + PARSER.declareStringArray(ConstructingObjectParser.constructorArg(), INDEX_PATTERNS); + PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), Template.PARSER, TEMPLATE); + PARSER.declareStringArray(ConstructingObjectParser.optionalConstructorArg(), COMPOSED_OF); + PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), PRIORITY); + PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), VERSION); + PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> p.map(), METADATA); + } + + private final List indexPatterns; + @Nullable + private final Template template; + @Nullable + private final List componentTemplates; + @Nullable + private final Long priority; + @Nullable + private final Long version; + @Nullable + private final Map metadata; + + static Diff readITV2DiffFrom(StreamInput in) throws IOException { + return AbstractDiffable.readDiffFrom(IndexTemplateV2::new, in); + } + + public static IndexTemplateV2 parse(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + public IndexTemplateV2(List indexPatterns, @Nullable Template template, @Nullable List componentTemplates, + @Nullable Long priority, @Nullable Long version, @Nullable Map metadata) { + this.indexPatterns = indexPatterns; + this.template = template; + this.componentTemplates = componentTemplates; + this.priority = priority; + this.version = version; + this.metadata = metadata; + } + + public IndexTemplateV2(StreamInput in) throws IOException { + this.indexPatterns = in.readStringList(); + if (in.readBoolean()) { + this.template = new Template(in); + } else { + this.template = null; + } + this.componentTemplates = in.readOptionalStringList(); + this.priority = in.readOptionalVLong(); + this.version = in.readOptionalVLong(); + this.metadata = in.readMap(); + } + + public List indexPatterns() { + return indexPatterns; + } + + public Template template() { + return template; + } + + public List composedOf() { + return componentTemplates; + } + + public Long priority() { + return priority; + } + + public Long version() { + return version; + } + + public Map metadata() { + return metadata; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeStringCollection(this.indexPatterns); + if (this.template == null) { + out.writeBoolean(false); + } else { + out.writeBoolean(true); + this.template.writeTo(out); + } + out.writeOptionalStringCollection(this.componentTemplates); + out.writeOptionalVLong(this.priority); + out.writeOptionalVLong(this.version); + out.writeMap(this.metadata); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field(INDEX_PATTERNS.getPreferredName(), this.indexPatterns); + if (this.template != null) { + builder.field(TEMPLATE.getPreferredName(), this.template); + } + if (this.componentTemplates != null) { + builder.field(COMPOSED_OF.getPreferredName(), this.componentTemplates); + } + if (this.priority != null) { + builder.field(PRIORITY.getPreferredName(), priority); + } + if (this.version != null) { + builder.field(VERSION.getPreferredName(), version); + } + if (this.metadata != null) { + builder.field(METADATA.getPreferredName(), metadata); + } + builder.endObject(); + return builder; + } + + @Override + public int hashCode() { + return Objects.hash(this.indexPatterns, this.template, this.componentTemplates, this.priority, this.version, this.metadata); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + IndexTemplateV2 other = (IndexTemplateV2) obj; + return Objects.equals(this.indexPatterns, other.indexPatterns) && + Objects.equals(this.template, other.template) && + Objects.equals(this.componentTemplates, other.componentTemplates) && + Objects.equals(this.priority, other.priority) && + Objects.equals(this.version, other.version) && + Objects.equals(this.metadata, other.metadata); + } + + @Override + public String toString() { + return Strings.toString(this); + } +} diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateV2Metadata.java b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateV2Metadata.java new file mode 100644 index 00000000000..42052d735b2 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexTemplateV2Metadata.java @@ -0,0 +1,170 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.cluster.metadata; + +import org.elasticsearch.Version; +import org.elasticsearch.cluster.Diff; +import org.elasticsearch.cluster.DiffableUtils; +import org.elasticsearch.cluster.NamedDiff; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * The {@link IndexTemplateV2Metadata} class is a custom {@link MetaData.Custom} implementation that + * stores a map of ids to {@link IndexTemplateV2} templates. + */ +public class IndexTemplateV2Metadata implements MetaData.Custom { + public static final String TYPE = "index_template"; + private static final ParseField INDEX_TEMPLATE = new ParseField("index_template"); + @SuppressWarnings("unchecked") + private static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(TYPE, false, + a -> new IndexTemplateV2Metadata((Map) a[0])); + + static { + PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, c) -> { + Map templates = new HashMap<>(); + while (p.nextToken() != XContentParser.Token.END_OBJECT) { + String name = p.currentName(); + templates.put(name, IndexTemplateV2.parse(p)); + } + return templates; + }, INDEX_TEMPLATE); + } + + private final Map indexTemplates; + + public IndexTemplateV2Metadata(Map templates) { + this.indexTemplates = templates; + } + + public IndexTemplateV2Metadata(StreamInput in) throws IOException { + this.indexTemplates = in.readMap(StreamInput::readString, IndexTemplateV2::new); + } + + public static IndexTemplateV2Metadata fromXContent(XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + public Map indexTemplates() { + return indexTemplates; + } + + @Override + public EnumSet context() { + return MetaData.ALL_CONTEXTS; + } + + @Override + public Diff diff(MetaData.Custom before) { + return new IndexTemplateV2MetadataDiff((IndexTemplateV2Metadata) before, this); + } + + public static NamedDiff readDiffFrom(StreamInput in) throws IOException { + return new IndexTemplateV2MetadataDiff(in); + } + + @Override + public String getWriteableName() { + return TYPE; + } + + @Override + public Version getMinimalSupportedVersion() { + return Version.V_7_7_0; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeMap(this.indexTemplates, StreamOutput::writeString, (outstream, val) -> val.writeTo(outstream)); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(INDEX_TEMPLATE.getPreferredName()); + for (Map.Entry template : indexTemplates.entrySet()) { + builder.field(template.getKey(), template.getValue()); + } + builder.endObject(); + return builder; + } + + @Override + public int hashCode() { + return Objects.hash(this.indexTemplates); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + IndexTemplateV2Metadata other = (IndexTemplateV2Metadata) obj; + return Objects.equals(this.indexTemplates, other.indexTemplates); + } + + @Override + public String toString() { + return Strings.toString(this); + } + + static class IndexTemplateV2MetadataDiff implements NamedDiff { + + final Diff> indexTemplateDiff; + + IndexTemplateV2MetadataDiff(IndexTemplateV2Metadata before, IndexTemplateV2Metadata after) { + this.indexTemplateDiff = DiffableUtils.diff(before.indexTemplates, after.indexTemplates, + DiffableUtils.getStringKeySerializer()); + } + + IndexTemplateV2MetadataDiff(StreamInput in) throws IOException { + this.indexTemplateDiff = DiffableUtils.readJdkMapDiff(in, DiffableUtils.getStringKeySerializer(), + IndexTemplateV2::new, IndexTemplateV2::readITV2DiffFrom); + } + + @Override + public MetaData.Custom apply(MetaData.Custom part) { + return new IndexTemplateV2Metadata(indexTemplateDiff.apply(((IndexTemplateV2Metadata) part).indexTemplates)); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + indexTemplateDiff.writeTo(out); + } + + @Override + public String getWriteableName() { + return TYPE; + } + } +} diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/MetaData.java b/server/src/main/java/org/elasticsearch/cluster/metadata/MetaData.java index d297cd97336..47f1c71b900 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/MetaData.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/MetaData.java @@ -727,6 +727,12 @@ public class MetaData implements Iterable, Diffable, To .orElse(Collections.emptyMap()); } + public Map templatesV2() { + return Optional.ofNullable((IndexTemplateV2Metadata) this.custom(IndexTemplateV2Metadata.TYPE)) + .map(IndexTemplateV2Metadata::indexTemplates) + .orElse(Collections.emptyMap()); + } + public ImmutableOpenMap customs() { return this.customs; } @@ -1163,6 +1169,34 @@ public class MetaData implements Iterable, Diffable, To return this; } + public Builder indexTemplates(Map indexTemplates) { + this.customs.put(IndexTemplateV2Metadata.TYPE, new IndexTemplateV2Metadata(indexTemplates)); + return this; + } + + public Builder put(String name, IndexTemplateV2 indexTemplate) { + Objects.requireNonNull(indexTemplate, "it is invalid to add a null index template: " + name); + // ಠ_ಠ at ImmutableOpenMap + Map existingTemplates = + Optional.ofNullable((IndexTemplateV2Metadata) this.customs.get(IndexTemplateV2Metadata.TYPE)) + .map(itmd -> new HashMap<>(itmd.indexTemplates())) + .orElse(new HashMap<>()); + existingTemplates.put(name, indexTemplate); + this.customs.put(IndexTemplateV2Metadata.TYPE, new IndexTemplateV2Metadata(existingTemplates)); + return this; + } + + public Builder removeIndexTemplate(String name) { + // ಠ_ಠ at ImmutableOpenMap + Map existingTemplates = + Optional.ofNullable((IndexTemplateV2Metadata) this.customs.get(IndexTemplateV2Metadata.TYPE)) + .map(itmd -> new HashMap<>(itmd.indexTemplates())) + .orElse(new HashMap<>()); + existingTemplates.remove(name); + this.customs.put(IndexTemplateV2Metadata.TYPE, new IndexTemplateV2Metadata(existingTemplates)); + return this; + } + public Custom getCustom(String type) { return customs.get(type); } diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/IndexTemplateV2MetadataTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/IndexTemplateV2MetadataTests.java new file mode 100644 index 00000000000..b479ef96cb5 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/IndexTemplateV2MetadataTests.java @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.cluster.metadata; + +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.test.AbstractNamedWriteableTestCase; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class IndexTemplateV2MetadataTests extends AbstractNamedWriteableTestCase { + @Override + protected IndexTemplateV2Metadata createTestInstance() { + if (randomBoolean()) { + return new IndexTemplateV2Metadata(Collections.emptyMap()); + } + Map templates = new HashMap<>(); + for (int i = 0; i < randomIntBetween(1, 5); i++) { + templates.put(randomAlphaOfLength(5), IndexTemplateV2Tests.randomInstance()); + } + return new IndexTemplateV2Metadata(templates); + } + + @Override + protected IndexTemplateV2Metadata mutateInstance(IndexTemplateV2Metadata instance) throws IOException { + return randomValueOtherThan(instance, this::createTestInstance); + } + + @Override + protected NamedWriteableRegistry getNamedWriteableRegistry() { + return new NamedWriteableRegistry(Collections.singletonList(new NamedWriteableRegistry.Entry(IndexTemplateV2Metadata.class, + IndexTemplateV2Metadata.TYPE, IndexTemplateV2Metadata::new))); + } + + @Override + protected Class categoryClass() { + return IndexTemplateV2Metadata.class; + } +} diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/IndexTemplateV2Tests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/IndexTemplateV2Tests.java new file mode 100644 index 00000000000..bafb28a0c29 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/IndexTemplateV2Tests.java @@ -0,0 +1,188 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.elasticsearch.cluster.metadata; + +import org.elasticsearch.cluster.Diff; +import org.elasticsearch.common.compress.CompressedXContent; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.test.AbstractDiffableSerializationTestCase; +import org.elasticsearch.test.ESTestCase; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class IndexTemplateV2Tests extends AbstractDiffableSerializationTestCase { + @Override + protected IndexTemplateV2 makeTestChanges(IndexTemplateV2 testInstance) { + try { + return mutateInstance(testInstance); + } catch (IOException e) { + logger.error(e); + fail("mutating should not throw an exception, but got: " + e); + return null; + } + } + + @Override + protected Writeable.Reader> diffReader() { + return IndexTemplateV2::readITV2DiffFrom; + } + + @Override + protected IndexTemplateV2 doParseInstance(XContentParser parser) throws IOException { + return IndexTemplateV2.parse(parser); + } + + @Override + protected Writeable.Reader instanceReader() { + return IndexTemplateV2::new; + } + + @Override + protected IndexTemplateV2 createTestInstance() { + return randomInstance(); + } + + public static IndexTemplateV2 randomInstance() { + Settings settings = null; + CompressedXContent mappings = null; + Map aliases = null; + Template template = null; + if (randomBoolean()) { + if (randomBoolean()) { + settings = randomSettings(); + } + if (randomBoolean()) { + mappings = randomMappings(); + } + if (randomBoolean()) { + aliases = randomAliases(); + } + template = new Template(settings, mappings, aliases); + } + + Map meta = null; + if (randomBoolean()) { + meta = randomMeta(); + } + + List indexPatterns = randomList(1, 4, () -> randomAlphaOfLength(4)); + List componentTemplates = randomList(0, 10, () -> randomAlphaOfLength(5)); + return new IndexTemplateV2(indexPatterns, + template, + componentTemplates, + randomBoolean() ? null : randomNonNegativeLong(), + randomBoolean() ? null : randomNonNegativeLong(), + meta); + } + + private static Map randomAliases() { + String aliasName = randomAlphaOfLength(5); + AliasMetaData aliasMeta = AliasMetaData.builder(aliasName) + .filter(Collections.singletonMap(randomAlphaOfLength(2), randomAlphaOfLength(2))) + .routing(randomBoolean() ? null : randomAlphaOfLength(3)) + .isHidden(randomBoolean() ? null : randomBoolean()) + .writeIndex(randomBoolean() ? null : randomBoolean()) + .build(); + return Collections.singletonMap(aliasName, aliasMeta); + } + + private static CompressedXContent randomMappings() { + try { + return new CompressedXContent("{\"" + randomAlphaOfLength(3) + "\":\"" + randomAlphaOfLength(7) + "\"}"); + } catch (IOException e) { + fail("got an IO exception creating fake mappings: " + e); + return null; + } + } + + private static Settings randomSettings() { + return Settings.builder() + .put(randomAlphaOfLength(4), randomAlphaOfLength(10)) + .build(); + } + + private static Map randomMeta() { + if (randomBoolean()) { + return Collections.singletonMap(randomAlphaOfLength(4), randomAlphaOfLength(4)); + } else { + return Collections.singletonMap(randomAlphaOfLength(5), + Collections.singletonMap(randomAlphaOfLength(4), randomAlphaOfLength(4))); + } + } + + @Override + protected IndexTemplateV2 mutateInstance(IndexTemplateV2 orig) throws IOException { + return mutateTemplate(orig); + } + + public static IndexTemplateV2 mutateTemplate(IndexTemplateV2 orig) { + switch (randomIntBetween(0, 5)) { + case 0: + List newIndexPatterns = randomValueOtherThan(orig.indexPatterns(), + () -> randomList(1, 4, () -> randomAlphaOfLength(4))); + return new IndexTemplateV2(newIndexPatterns, orig.template(), orig.composedOf(), + orig.priority(), orig.version(), orig.metadata()); + case 1: + return new IndexTemplateV2(orig.indexPatterns(), + randomValueOtherThan(orig.template(), () -> new Template(randomSettings(), randomMappings(), randomAliases())), + orig.composedOf(), + orig.priority(), + orig.version(), + orig.metadata()); + case 2: + List newComposedOf = randomValueOtherThan(orig.composedOf(), + () -> randomList(0, 10, () -> randomAlphaOfLength(5))); + return new IndexTemplateV2(orig.indexPatterns(), + orig.template(), + newComposedOf, + orig.priority(), + orig.version(), + orig.metadata()); + case 3: + return new IndexTemplateV2(orig.indexPatterns(), + orig.template(), + orig.composedOf(), + randomValueOtherThan(orig.priority(), ESTestCase::randomNonNegativeLong), + orig.version(), + orig.metadata()); + case 4: + return new IndexTemplateV2(orig.indexPatterns(), + orig.template(), + orig.composedOf(), + orig.priority(), + randomValueOtherThan(orig.version(), ESTestCase::randomNonNegativeLong), + orig.metadata()); + case 5: + return new IndexTemplateV2(orig.indexPatterns(), + orig.template(), + orig.composedOf(), + orig.priority(), + orig.version(), + randomValueOtherThan(orig.metadata(), IndexTemplateV2Tests::randomMeta)); + default: + throw new IllegalStateException("illegal randomization branch"); + } + } +} diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/MetaDataTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/MetaDataTests.java index e34d9aebb3b..dd3eb5b9e8f 100644 --- a/server/src/test/java/org/elasticsearch/cluster/metadata/MetaDataTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/MetaDataTests.java @@ -972,6 +972,7 @@ public class MetaDataTests extends ESTestCase { .indexGraveyard(IndexGraveyardTests.createRandom()) .version(randomNonNegativeLong()) .put("component_template_" + randomAlphaOfLength(3), ComponentTemplateTests.randomInstance()) + .put("index_template_v2_" + randomAlphaOfLength(3), IndexTemplateV2Tests.randomInstance()) .build(); } } diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/ToAndFromJsonMetaDataTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/ToAndFromJsonMetaDataTests.java index ffcc827115e..527be9b361c 100644 --- a/server/src/test/java/org/elasticsearch/cluster/metadata/ToAndFromJsonMetaDataTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/ToAndFromJsonMetaDataTests.java @@ -26,6 +26,7 @@ import org.elasticsearch.common.xcontent.json.JsonXContent; import org.elasticsearch.test.ESTestCase; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import static org.elasticsearch.cluster.metadata.AliasMetaData.newAliasMetaDataBuilder; @@ -128,6 +129,14 @@ public class ToAndFromJsonMetaDataTests extends ESTestCase { new CompressedXContent("{\"baz\":\"eggplant\"}"), Collections.singletonMap("alias", AliasMetaData.builder("alias").build())), 5L, Collections.singletonMap("my_meta", Collections.singletonMap("foo", "bar")))) + .put("index_templatev2", new IndexTemplateV2(Arrays.asList("foo", "bar*"), + new Template(Settings.builder().put("setting", "value").build(), + new CompressedXContent("{\"baz\":\"eggplant\"}"), + Collections.singletonMap("alias", AliasMetaData.builder("alias").build())), + Collections.singletonList("component_template"), + 5L, + 4L, + Collections.singletonMap("my_meta", Collections.singletonMap("potato", "chicken")))) .put(IndexMetaData.builder("test12") .settings(settings(Version.CURRENT) .put("setting1", "value1") @@ -323,6 +332,19 @@ public class ToAndFromJsonMetaDataTests extends ESTestCase { equalTo(new Template(Settings.builder().put("setting", "value").build(), new CompressedXContent("{\"baz\":\"eggplant\"}"), Collections.singletonMap("alias", AliasMetaData.builder("alias").build())))); + + // index template v2 + assertNotNull(parsedMetaData.templatesV2().get("index_templatev2")); + assertThat(parsedMetaData.templatesV2().get("index_templatev2").priority(), is(5L)); + assertThat(parsedMetaData.templatesV2().get("index_templatev2").version(), is(4L)); + assertThat(parsedMetaData.templatesV2().get("index_templatev2").indexPatterns(), is(Arrays.asList("foo", "bar*"))); + assertThat(parsedMetaData.templatesV2().get("index_templatev2").composedOf(), is(Collections.singletonList("component_template"))); + assertThat(parsedMetaData.templatesV2().get("index_templatev2").metadata(), + equalTo(Collections.singletonMap("my_meta", Collections.singletonMap("potato", "chicken")))); + assertThat(parsedMetaData.templatesV2().get("index_templatev2").template(), + equalTo(new Template(Settings.builder().put("setting", "value").build(), + new CompressedXContent("{\"baz\":\"eggplant\"}"), + Collections.singletonMap("alias", AliasMetaData.builder("alias").build())))); } private static final String MAPPING_SOURCE1 = "{\"mapping1\":{\"text1\":{\"type\":\"string\"}}}";