[7.x] Add ComponentTemplate to MetaData (#53290) (#53489)

* Add ComponentTemplate to MetaData (#53290)

* Add ComponentTemplate to MetaData

This adds a `ComponentTemplate` datastructure that will be used as part of #53101 (Index Templates
v2) to the `MetaData` class. Currently there are no APIs for interacting with this class, so it will
always be an empty map (other than in tests). This infrastructure will be built upon to add APIs in
a subsequent commit.

A `ComponentTemplate` is made up of a `Template`, a version, and a MetaData.Custom class. The
`Template` contains similar information to an `IndexTemplateMetaData` object— settings, mappings,
and alias configuration.

* Update minimal supported version constant

Co-authored-by: Elastic Machine <elasticmachine@users.noreply.github.com>
This commit is contained in:
Lee Hinman 2020-03-12 15:33:32 -06:00 committed by GitHub
parent 9dcd64c110
commit 2789fe4179
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 793 additions and 1 deletions

View File

@ -22,6 +22,7 @@ package org.elasticsearch.cluster;
import org.elasticsearch.cluster.action.index.MappingUpdatedAction;
import org.elasticsearch.cluster.action.index.NodeMappingRefreshAction;
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.MetaData;
@ -130,6 +131,8 @@ public class ClusterModule extends AbstractModule {
registerMetaDataCustom(entries, IndexGraveyard.TYPE, IndexGraveyard::new, IndexGraveyard::readDiffFrom);
registerMetaDataCustom(entries, PersistentTasksCustomMetaData.TYPE, PersistentTasksCustomMetaData::new,
PersistentTasksCustomMetaData::readDiffFrom);
registerMetaDataCustom(entries, ComponentTemplateMetadata.TYPE, ComponentTemplateMetadata::new,
ComponentTemplateMetadata::readDiffFrom);
// Task Status (not Diffable)
entries.add(new Entry(Task.Status.class, PersistentTasksNodeService.Status.NAME, PersistentTasksNodeService.Status::new));
return entries;
@ -177,6 +180,8 @@ public class ClusterModule extends AbstractModule {
IndexGraveyard::fromXContent));
entries.add(new NamedXContentRegistry.Entry(MetaData.Custom.class, new ParseField(PersistentTasksCustomMetaData.TYPE),
PersistentTasksCustomMetaData::fromXContent));
entries.add(new NamedXContentRegistry.Entry(MetaData.Custom.class, new ParseField(ComponentTemplateMetadata.TYPE),
ComponentTemplateMetadata::fromXContent));
return entries;
}

View File

@ -0,0 +1,300 @@
/*
* 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.bytes.BytesArray;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
import org.elasticsearch.common.xcontent.ToXContentObject;
import org.elasticsearch.common.xcontent.XContentBuilder;
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 java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
/**
* A component template is a re-usable template as well as metadata about the template. Each
* component template is expected to be valid on its own. For example, if a component template
* contains a field "foo", it's expected to contain all the necessary settings/mappings/etc for the
* "foo" field. These component templates make up the individual pieces composing an index template.
*/
public class ComponentTemplate extends AbstractDiffable<ComponentTemplate> implements ToXContentObject {
private static final ParseField TEMPLATE = new ParseField("template");
private static final ParseField VERSION = new ParseField("version");
private static final ParseField METADATA = new ParseField("_meta");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<ComponentTemplate, Void> PARSER =
new ConstructingObjectParser<>("component_template", false,
a -> new ComponentTemplate((Template) a[0], (Long) a[1], (Map<String, Object>) a[2]));
static {
PARSER.declareObject(ConstructingObjectParser.constructorArg(), Template.PARSER, TEMPLATE);
PARSER.declareLong(ConstructingObjectParser.optionalConstructorArg(), VERSION);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> p.map(), METADATA);
}
private final Template template;
@Nullable
private final Long version;
@Nullable
private final Map<String, Object> metadata;
static Diff<ComponentTemplate> readComponentTemplateDiffFrom(StreamInput in) throws IOException {
return AbstractDiffable.readDiffFrom(ComponentTemplate::new, in);
}
public static ComponentTemplate parse(XContentParser parser) {
return PARSER.apply(parser, null);
}
public ComponentTemplate(Template template, @Nullable Long version, @Nullable Map<String, Object> metadata) {
this.template = template;
this.version = version;
this.metadata = metadata;
}
public ComponentTemplate(StreamInput in) throws IOException {
this.template = new Template(in);
this.version = in.readOptionalVLong();
if (in.readBoolean()) {
this.metadata = in.readMap();
} else {
this.metadata = null;
}
}
public Template template() {
return template;
}
@Nullable
public Long version() {
return version;
}
@Nullable
public Map<String, Object> metadata() {
return metadata;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
this.template.writeTo(out);
out.writeOptionalVLong(this.version);
if (this.metadata == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
out.writeMap(this.metadata);
}
}
@Override
public int hashCode() {
return Objects.hash(template, version, metadata);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != getClass()) {
return false;
}
ComponentTemplate other = (ComponentTemplate) obj;
return Objects.equals(template, other.template) &&
Objects.equals(version, other.version) &&
Objects.equals(metadata, other.metadata);
}
@Override
public String toString() {
return Strings.toString(this);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field(TEMPLATE.getPreferredName(), this.template);
if (this.version != null) {
builder.field(VERSION.getPreferredName(), this.version);
}
if (this.metadata != null) {
builder.field(METADATA.getPreferredName(), this.metadata);
}
builder.endObject();
return builder;
}
static class Template extends AbstractDiffable<Template> implements ToXContentObject {
private static final ParseField SETTINGS = new ParseField("settings");
private static final ParseField MAPPINGS = new ParseField("mappings");
private static final ParseField ALIASES = new ParseField("aliases");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<Template, Void> PARSER = new ConstructingObjectParser<>("template", false,
a -> new Template((Settings) a[0], (CompressedXContent) a[1], (Map<String, AliasMetaData>) a[2]));
static {
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> Settings.fromXContent(p), SETTINGS);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) ->
new CompressedXContent(Strings.toString(XContentFactory.jsonBuilder().map(p.mapOrdered()))), MAPPINGS);
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(), (p, c) -> {
Map<String, AliasMetaData> aliasMap = new HashMap<>();
while ((p.nextToken()) != XContentParser.Token.END_OBJECT) {
AliasMetaData alias = AliasMetaData.Builder.fromXContent(p);
aliasMap.put(alias.alias(), alias);
}
return aliasMap;
}, ALIASES);
}
@Nullable
private final Settings settings;
@Nullable
private final CompressedXContent mappings;
@Nullable
private final Map<String, AliasMetaData> aliases;
Template(@Nullable Settings settings, @Nullable CompressedXContent mappings, @Nullable Map<String, AliasMetaData> aliases) {
this.settings = settings;
this.mappings = mappings;
this.aliases = aliases;
}
Template(StreamInput in) throws IOException {
if (in.readBoolean()) {
this.settings = Settings.readSettingsFromStream(in);
} else {
this.settings = null;
}
if (in.readBoolean()) {
this.mappings = CompressedXContent.readCompressedString(in);
} else {
this.mappings = null;
}
if (in.readBoolean()) {
this.aliases = in.readMap(StreamInput::readString, AliasMetaData::new);
} else {
this.aliases = null;
}
}
public Settings settings() {
return settings;
}
public CompressedXContent mappings() {
return mappings;
}
public Map<String, AliasMetaData> aliases() {
return aliases;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
if (this.settings == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
Settings.writeSettingsToStream(this.settings, out);
}
if (this.mappings == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
this.mappings.writeTo(out);
}
if (this.aliases == null) {
out.writeBoolean(false);
} else {
out.writeBoolean(true);
out.writeMap(this.aliases, StreamOutput::writeString, (stream, aliasMetaData) -> aliasMetaData.writeTo(stream));
}
}
@Override
public int hashCode() {
return Objects.hash(settings, mappings, aliases);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != getClass()) {
return false;
}
Template other = (Template) obj;
return Objects.equals(settings, other.settings) &&
Objects.equals(mappings, other.mappings) &&
Objects.equals(aliases, other.aliases);
}
@Override
public String toString() {
return Strings.toString(this);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
if (this.settings != null) {
builder.startObject(SETTINGS.getPreferredName());
this.settings.toXContent(builder, params);
builder.endObject();
}
if (this.mappings != null) {
Map<String, Object> uncompressedMapping =
XContentHelper.convertToMap(new BytesArray(this.mappings.uncompressed()), true, XContentType.JSON).v2();
if (uncompressedMapping.size() > 0) {
builder.field(MAPPINGS.getPreferredName());
builder.map(uncompressedMapping);
}
}
if (this.aliases != null) {
builder.startObject(ALIASES.getPreferredName());
for (AliasMetaData alias : this.aliases.values()) {
AliasMetaData.Builder.toXContent(alias, builder, params);
}
builder.endObject();
}
builder.endObject();
return builder;
}
}
}

View File

@ -0,0 +1,169 @@
/*
* 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;
/**
* {@link ComponentTemplateMetadata} is a custom {@link MetaData} implementation for storing a map
* of component templates and their names.
*/
public class ComponentTemplateMetadata implements MetaData.Custom {
public static final String TYPE = "component_template";
private static final ParseField COMPONENT_TEMPLATE = new ParseField("component_template");
@SuppressWarnings("unchecked")
private static final ConstructingObjectParser<ComponentTemplateMetadata, Void> PARSER = new ConstructingObjectParser<>(TYPE, false,
a -> new ComponentTemplateMetadata((Map<String, ComponentTemplate>) a[0]));
static {
PARSER.declareObject(ConstructingObjectParser.constructorArg(), (p, c) -> {
Map<String, ComponentTemplate> templates = new HashMap<>();
while (p.nextToken() != XContentParser.Token.END_OBJECT) {
String name = p.currentName();
templates.put(name, ComponentTemplate.parse(p));
}
return templates;
}, COMPONENT_TEMPLATE);
}
private final Map<String, ComponentTemplate> componentTemplates;
public ComponentTemplateMetadata(Map<String, ComponentTemplate> componentTemplates) {
this.componentTemplates = componentTemplates;
}
public ComponentTemplateMetadata(StreamInput in) throws IOException {
this.componentTemplates = in.readMap(StreamInput::readString, ComponentTemplate::new);
}
public Map<String, ComponentTemplate> componentTemplates() {
return this.componentTemplates;
}
@Override
public Diff<MetaData.Custom> diff(MetaData.Custom before) {
return new ComponentTemplateMetadataDiff((ComponentTemplateMetadata) before, this);
}
public static NamedDiff<MetaData.Custom> readDiffFrom(StreamInput in) throws IOException {
return new ComponentTemplateMetadataDiff(in);
}
@Override
public EnumSet<MetaData.XContentContext> context() {
return MetaData.ALL_CONTEXTS;
}
@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.componentTemplates, StreamOutput::writeString, (stream, val) -> val.writeTo(stream));
}
public static ComponentTemplateMetadata fromXContent(XContentParser parser) throws IOException {
return PARSER.parse(parser, null);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject(COMPONENT_TEMPLATE.getPreferredName());
for (Map.Entry<String, ComponentTemplate> template : componentTemplates.entrySet()) {
builder.field(template.getKey(), template.getValue());
}
builder.endObject();
return builder;
}
@Override
public int hashCode() {
return Objects.hash(this.componentTemplates);
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (obj.getClass() != getClass()) {
return false;
}
ComponentTemplateMetadata other = (ComponentTemplateMetadata) obj;
return Objects.equals(this.componentTemplates, other.componentTemplates);
}
@Override
public String toString() {
return Strings.toString(this);
}
static class ComponentTemplateMetadataDiff implements NamedDiff<MetaData.Custom> {
final Diff<Map<String, ComponentTemplate>> componentTemplateDiff;
ComponentTemplateMetadataDiff(ComponentTemplateMetadata before, ComponentTemplateMetadata after) {
this.componentTemplateDiff = DiffableUtils.diff(before.componentTemplates, after.componentTemplates,
DiffableUtils.getStringKeySerializer());
}
ComponentTemplateMetadataDiff(StreamInput in) throws IOException {
this.componentTemplateDiff = DiffableUtils.readJdkMapDiff(in, DiffableUtils.getStringKeySerializer(),
ComponentTemplate::new, ComponentTemplate::readComponentTemplateDiffFrom);
}
@Override
public MetaData.Custom apply(MetaData.Custom part) {
return new ComponentTemplateMetadata(componentTemplateDiff.apply(((ComponentTemplateMetadata) part).componentTemplates));
}
@Override
public void writeTo(StreamOutput out) throws IOException {
componentTemplateDiff.writeTo(out);
}
@Override
public String getWriteableName() {
return TYPE;
}
}
}

View File

@ -75,6 +75,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
@ -720,6 +721,12 @@ public class MetaData implements Iterable<IndexMetaData>, Diffable<MetaData>, To
return this.templates;
}
public Map<String, ComponentTemplate> componentTemplates() {
return Optional.ofNullable((ComponentTemplateMetadata) this.custom(ComponentTemplateMetadata.TYPE))
.map(ComponentTemplateMetadata::componentTemplates)
.orElse(Collections.emptyMap());
}
public ImmutableOpenMap<String, Custom> customs() {
return this.customs;
}
@ -1128,6 +1135,34 @@ public class MetaData implements Iterable<IndexMetaData>, Diffable<MetaData>, To
return this;
}
public Builder put(String name, ComponentTemplate componentTemplate) {
Objects.requireNonNull(componentTemplate, "it is invalid to add a null component template: " + name);
// ಠ_ಠ at ImmutableOpenMap
Map<String, ComponentTemplate> existingTemplates =
Optional.ofNullable((ComponentTemplateMetadata) this.customs.get(ComponentTemplateMetadata.TYPE))
.map(ctm -> new HashMap<>(ctm.componentTemplates()))
.orElse(new HashMap<>());
existingTemplates.put(name, componentTemplate);
this.customs.put(ComponentTemplateMetadata.TYPE, new ComponentTemplateMetadata(existingTemplates));
return this;
}
public Builder removeComponentTemplate(String name) {
// ಠ_ಠ at ImmutableOpenMap
Map<String, ComponentTemplate> existingTemplates =
Optional.ofNullable((ComponentTemplateMetadata) this.customs.get(ComponentTemplateMetadata.TYPE))
.map(ctm -> new HashMap<>(ctm.componentTemplates()))
.orElse(new HashMap<>());
existingTemplates.remove(name);
this.customs.put(ComponentTemplateMetadata.TYPE, new ComponentTemplateMetadata(existingTemplates));
return this;
}
public Builder componentTemplates(Map<String, ComponentTemplate> componentTemplates) {
this.customs.put(ComponentTemplateMetadata.TYPE, new ComponentTemplateMetadata(componentTemplates));
return this;
}
public Custom getCustom(String type) {
return customs.get(type);
}

View File

@ -0,0 +1,70 @@
/*
* 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 ComponentTemplateMetadataTests extends AbstractNamedWriteableTestCase<ComponentTemplateMetadata> {
@Override
protected ComponentTemplateMetadata createTestInstance() {
int count = randomIntBetween(0, 3);
Map<String, ComponentTemplate> templateMap = new HashMap<>(count);
for (int i = 0; i < count; i++) {
templateMap.put(randomAlphaOfLength(4), ComponentTemplateTests.randomInstance());
}
return new ComponentTemplateMetadata(templateMap);
}
@Override
protected ComponentTemplateMetadata mutateInstance(ComponentTemplateMetadata instance) throws IOException {
if (instance.componentTemplates().size() == 0) {
// Not really much to mutate, so just generate a new one
return randomValueOtherThan(instance, this::createTestInstance);
}
Map<String, ComponentTemplate> templates = new HashMap<>(instance.componentTemplates());
Map.Entry<String, ComponentTemplate> newTemplate = instance.componentTemplates().entrySet().iterator().next();
if (randomBoolean()) {
// Change the key
templates.put(randomAlphaOfLength(4), newTemplate.getValue());
} else {
// Change the value
templates.put(newTemplate.getKey(), ComponentTemplateTests.mutateTemplate(newTemplate.getValue()));
}
return new ComponentTemplateMetadata(templates);
}
@Override
protected NamedWriteableRegistry getNamedWriteableRegistry() {
return new NamedWriteableRegistry(Collections.singletonList(new NamedWriteableRegistry.Entry(ComponentTemplateMetadata.class,
ComponentTemplateMetadata.TYPE, ComponentTemplateMetadata::new)));
}
@Override
protected Class<ComponentTemplateMetadata> categoryClass() {
return ComponentTemplateMetadata.class;
}
}

View File

@ -0,0 +1,163 @@
/*
* 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.Map;
public class ComponentTemplateTests extends AbstractDiffableSerializationTestCase<ComponentTemplate> {
@Override
protected ComponentTemplate makeTestChanges(ComponentTemplate 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<Diff<ComponentTemplate>> diffReader() {
return ComponentTemplate::readComponentTemplateDiffFrom;
}
@Override
protected ComponentTemplate doParseInstance(XContentParser parser) throws IOException {
return ComponentTemplate.parse(parser);
}
@Override
protected Writeable.Reader<ComponentTemplate> instanceReader() {
return ComponentTemplate::new;
}
@Override
protected ComponentTemplate createTestInstance() {
return randomInstance();
}
public static ComponentTemplate randomInstance() {
Settings settings = null;
CompressedXContent mappings = null;
Map<String, AliasMetaData> aliases = null;
if (randomBoolean()) {
settings = randomSettings();
}
if (randomBoolean()) {
mappings = randomMappings();
}
if (randomBoolean()) {
aliases = randomAliases();
}
ComponentTemplate.Template template = new ComponentTemplate.Template(settings, mappings, aliases);
Map<String, Object> meta = null;
if (randomBoolean()) {
meta = randomMeta();
}
return new ComponentTemplate(template, randomBoolean() ? null : randomNonNegativeLong(), meta);
}
private static Map<String, AliasMetaData> 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<String, Object> randomMeta() {
if (randomBoolean()) {
return Collections.singletonMap(randomAlphaOfLength(4), randomAlphaOfLength(4));
} else {
return Collections.singletonMap(randomAlphaOfLength(5),
Collections.singletonMap(randomAlphaOfLength(4), randomAlphaOfLength(4)));
}
}
@Override
protected ComponentTemplate mutateInstance(ComponentTemplate orig) throws IOException {
return mutateTemplate(orig);
}
public static ComponentTemplate mutateTemplate(ComponentTemplate orig) {
switch (randomIntBetween(0, 2)) {
case 0:
switch (randomIntBetween(0, 2)) {
case 0:
ComponentTemplate.Template ot = orig.template();
return new ComponentTemplate(
new ComponentTemplate.Template(randomValueOtherThan(ot.settings(), ComponentTemplateTests::randomSettings),
ot.mappings(), ot.aliases()),
orig.version(), orig.metadata());
case 1:
ComponentTemplate.Template ot2 = orig.template();
return new ComponentTemplate(
new ComponentTemplate.Template(ot2.settings(),
randomValueOtherThan(ot2.mappings(), ComponentTemplateTests::randomMappings), ot2.aliases()),
orig.version(), orig.metadata());
case 2:
ComponentTemplate.Template ot3 = orig.template();
return new ComponentTemplate(
new ComponentTemplate.Template(ot3.settings(), ot3.mappings(),
randomValueOtherThan(ot3.aliases(), ComponentTemplateTests::randomAliases)),
orig.version(), orig.metadata());
default:
throw new IllegalStateException("illegal randomization branch");
}
case 1:
return new ComponentTemplate(orig.template(), randomValueOtherThan(orig.version(), ESTestCase::randomNonNegativeLong),
orig.metadata());
case 2:
return new ComponentTemplate(orig.template(), orig.version(),
randomValueOtherThan(orig.metadata(), ComponentTemplateTests::randomMeta));
default:
throw new IllegalStateException("illegal randomization branch");
}
}
}

View File

@ -44,6 +44,7 @@ import org.elasticsearch.plugins.MapperPlugin;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@ -750,7 +751,7 @@ public class MetaDataTests extends ESTestCase {
}
}
private IndexMetaData.Builder buildIndexMetaData(String name, String alias, Boolean writeIndex) {
private static IndexMetaData.Builder buildIndexMetaData(String name, String alias, Boolean writeIndex) {
return IndexMetaData.builder(name)
.settings(settings(Version.CURRENT)).creationDate(randomNonNegativeLong())
.putAlias(AliasMetaData.builder(alias).writeIndex(writeIndex))
@ -940,4 +941,37 @@ public class MetaDataTests extends ESTestCase {
final ImmutableOpenMap<String, MetaData.Custom> map = mapBuilder.build();
assertThat(expectThrows(NullPointerException.class, () -> builder.customs(map)).getMessage(), containsString(key));
}
public void testSerialization() throws IOException {
final MetaData orig = randomMetaData();
final BytesStreamOutput out = new BytesStreamOutput();
orig.writeTo(out);
NamedWriteableRegistry namedWriteableRegistry = new NamedWriteableRegistry(ClusterModule.getNamedWriteables());
final MetaData fromStreamMeta = MetaData.readFrom(new NamedWriteableAwareStreamInput(out.bytes().streamInput(),
namedWriteableRegistry));
assertTrue(MetaData.isGlobalStateEquals(orig, fromStreamMeta));
}
public static MetaData randomMetaData() {
return MetaData.builder()
.put(buildIndexMetaData("index", "alias", randomBoolean() ? null : randomBoolean()).build(), randomBoolean())
.put(IndexTemplateMetaData.builder("template" + randomAlphaOfLength(3))
.patterns(Arrays.asList("bar-*", "foo-*"))
.settings(Settings.builder()
.put("random_index_setting_" + randomAlphaOfLength(3), randomAlphaOfLength(5))
.build())
.build())
.persistentSettings(Settings.builder()
.put("setting" + randomAlphaOfLength(3), randomAlphaOfLength(4))
.build())
.transientSettings(Settings.builder()
.put("other_setting" + randomAlphaOfLength(3), randomAlphaOfLength(4))
.build())
.clusterUUID("uuid" + randomAlphaOfLength(3))
.clusterUUIDCommitted(randomBoolean())
.indexGraveyard(IndexGraveyardTests.createRandom())
.version(randomNonNegativeLong())
.put("component_template_" + randomAlphaOfLength(3), ComponentTemplateTests.randomInstance())
.build();
}
}

View File

@ -20,6 +20,7 @@
package org.elasticsearch.cluster.metadata;
import org.elasticsearch.Version;
import org.elasticsearch.common.compress.CompressedXContent;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.json.JsonXContent;
import org.elasticsearch.test.ESTestCase;
@ -122,6 +123,11 @@ public class ToAndFromJsonMetaDataTests extends ESTestCase {
.putAlias(newAliasMetaDataBuilder("alias-bar1"))
.putAlias(newAliasMetaDataBuilder("alias-bar2").filter("{\"term\":{\"user\":\"kimchy\"}}"))
.putAlias(newAliasMetaDataBuilder("alias-bar3").routing("routing-bar")))
.put("component_template", new ComponentTemplate(
new ComponentTemplate.Template(Settings.builder().put("setting", "value").build(),
new CompressedXContent("{\"baz\":\"eggplant\"}"),
Collections.singletonMap("alias", AliasMetaData.builder("alias").build())),
5L, Collections.singletonMap("my_meta", Collections.singletonMap("foo", "bar"))))
.put(IndexMetaData.builder("test12")
.settings(settings(Version.CURRENT)
.put("setting1", "value1")
@ -307,6 +313,16 @@ public class ToAndFromJsonMetaDataTests extends ESTestCase {
assertThat(parsedMetaData.templates().get("foo").aliases().get("alias-bar3").alias(), equalTo("alias-bar3"));
assertThat(parsedMetaData.templates().get("foo").aliases().get("alias-bar3").indexRouting(), equalTo("routing-bar"));
assertThat(parsedMetaData.templates().get("foo").aliases().get("alias-bar3").searchRouting(), equalTo("routing-bar"));
// component template
assertNotNull(parsedMetaData.componentTemplates().get("component_template"));
assertThat(parsedMetaData.componentTemplates().get("component_template").version(), is(5L));
assertThat(parsedMetaData.componentTemplates().get("component_template").metadata(),
equalTo(Collections.singletonMap("my_meta", Collections.singletonMap("foo", "bar"))));
assertThat(parsedMetaData.componentTemplates().get("component_template").template(),
equalTo(new ComponentTemplate.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\"}}}";