Add "version" field to Templates

This adds a version field to Templates, which is itself is unused by Elasticsearch, but exists for users to better manage their own templates. Like description, it's optional.
This commit is contained in:
Chris Earle 2016-09-06 20:32:31 -04:00
parent 51de39f21b
commit 07ccabbebc
9 changed files with 254 additions and 29 deletions

View File

@ -27,9 +27,7 @@ import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import static java.util.Collections.singletonMap;

View File

@ -74,6 +74,8 @@ public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateR
private Map<String, IndexMetaData.Custom> customs = new HashMap<>();
private Integer version;
public PutIndexTemplateRequest() {
}
@ -129,6 +131,15 @@ public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateR
return this.order;
}
public PutIndexTemplateRequest version(Integer version) {
this.version = version;
return this;
}
public Integer version() {
return this.version;
}
/**
* Set to <tt>true</tt> to force only creation, not an update of an index template. If it already
* exists, it will fail with an {@link org.elasticsearch.indices.IndexTemplateAlreadyExistsException}.
@ -278,16 +289,23 @@ public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateR
template(entry.getValue().toString());
} else if (name.equals("order")) {
order(XContentMapValues.nodeIntegerValue(entry.getValue(), order()));
} else if ("version".equals(name)) {
if ((entry.getValue() instanceof Integer) == false) {
throw new IllegalArgumentException("Malformed [version] value, should be an integer");
}
version((Integer)entry.getValue());
} else if (name.equals("settings")) {
if (!(entry.getValue() instanceof Map)) {
throw new IllegalArgumentException("Malformed settings section, should include an inner object");
throw new IllegalArgumentException("Malformed [settings] section, should include an inner object");
}
settings((Map<String, Object>) entry.getValue());
} else if (name.equals("mappings")) {
Map<String, Object> mappings = (Map<String, Object>) entry.getValue();
for (Map.Entry<String, Object> entry1 : mappings.entrySet()) {
if (!(entry1.getValue() instanceof Map)) {
throw new IllegalArgumentException("Malformed mappings section for type [" + entry1.getKey() + "], should include an inner object describing the mapping");
throw new IllegalArgumentException(
"Malformed [mappings] section for type [" + entry1.getKey() +
"], should include an inner object describing the mapping");
}
mapping(entry1.getKey(), (Map<String, Object>) entry1.getValue());
}
@ -449,6 +467,7 @@ public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateR
for (int i = 0; i < aliasesSize; i++) {
aliases.add(Alias.read(in));
}
version = in.readOptionalVInt();
}
@Override
@ -474,5 +493,6 @@ public class PutIndexTemplateRequest extends MasterNodeRequest<PutIndexTemplateR
for (Alias alias : aliases) {
alias.writeTo(out);
}
out.writeOptionalVInt(version);
}
}

View File

@ -30,7 +30,8 @@ import java.util.Map;
/**
*
*/
public class PutIndexTemplateRequestBuilder extends MasterNodeOperationRequestBuilder<PutIndexTemplateRequest, PutIndexTemplateResponse, PutIndexTemplateRequestBuilder> {
public class PutIndexTemplateRequestBuilder
extends MasterNodeOperationRequestBuilder<PutIndexTemplateRequest, PutIndexTemplateResponse, PutIndexTemplateRequestBuilder> {
public PutIndexTemplateRequestBuilder(ElasticsearchClient client, PutIndexTemplateAction action) {
super(client, action, new PutIndexTemplateRequest());
@ -56,6 +57,14 @@ public class PutIndexTemplateRequestBuilder extends MasterNodeOperationRequestBu
return this;
}
/**
* Sets the optional version of this template.
*/
public PutIndexTemplateRequestBuilder setVersion(Integer version) {
request.version(version);
return this;
}
/**
* Set to <tt>true</tt> to force only creation, not an update of an index template. If it already
* exists, it will fail with an {@link org.elasticsearch.indices.IndexTemplateAlreadyExistsException}.

View File

@ -86,7 +86,8 @@ public class TransportPutIndexTemplateAction extends TransportMasterNodeAction<P
.aliases(request.aliases())
.customs(request.customs())
.create(request.create())
.masterTimeout(request.masterNodeTimeout()),
.masterTimeout(request.masterNodeTimeout())
.version(request.version()),
new MetaDataIndexTemplateService.PutListener() {
@Override

View File

@ -21,7 +21,9 @@ package org.elasticsearch.cluster.metadata;
import com.carrotsearch.hppc.cursors.ObjectCursor;
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.AbstractDiffable;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.compress.CompressedXContent;
@ -37,6 +39,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/**
@ -50,6 +53,26 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
private final int order;
/**
* The version is an arbitrary number managed by the user so that they can easily and quickly verify the existence of a given template.
* Expected usage:
* <pre><code>
* PUT /_template/my_template
* {
* "template": "my_index-*",
* "mappings": { ... },
* "version": 1
* }
* </code></pre>
* Then, some process from the user can occasionally verify that the template exists with the appropriate version without having to
* check the template's content:
* <pre><code>
* GET /_template/my_template?filter_path=*.version
* </code></pre>
*/
@Nullable
private final Integer version;
private final String template;
private final Settings settings;
@ -61,10 +84,14 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
private final ImmutableOpenMap<String, IndexMetaData.Custom> customs;
public IndexTemplateMetaData(String name, int order, String template, Settings settings, ImmutableOpenMap<String, CompressedXContent> mappings,
ImmutableOpenMap<String, AliasMetaData> aliases, ImmutableOpenMap<String, IndexMetaData.Custom> customs) {
public IndexTemplateMetaData(String name, int order, Integer version,
String template, Settings settings,
ImmutableOpenMap<String, CompressedXContent> mappings,
ImmutableOpenMap<String, AliasMetaData> aliases,
ImmutableOpenMap<String, IndexMetaData.Custom> customs) {
this.name = name;
this.order = order;
this.version = version;
this.template = template;
this.settings = settings;
this.mappings = mappings;
@ -84,6 +111,16 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
return order();
}
@Nullable
public Integer getVersion() {
return version();
}
@Nullable
public Integer version() {
return version;
}
public String getName() {
return this.name;
}
@ -150,13 +187,14 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
if (!settings.equals(that.settings)) return false;
if (!template.equals(that.template)) return false;
return true;
return Objects.equals(version, that.version);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + order;
result = 31 * result + Objects.hashCode(version);
result = 31 * result + template.hashCode();
result = 31 * result + settings.hashCode();
result = 31 * result + mappings.hashCode();
@ -184,6 +222,9 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
IndexMetaData.Custom customIndexMetaData = IndexMetaData.lookupPrototypeSafe(type).readFrom(in);
builder.putCustom(type, customIndexMetaData);
}
if (in.getVersion().onOrAfter(Version.V_5_0_0_alpha6)) {
builder.version(in.readOptionalVInt());
}
return builder.build();
}
@ -207,6 +248,9 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
out.writeString(cursor.key);
cursor.value.writeTo(out);
}
if (out.getVersion().onOrAfter(Version.V_5_0_0_alpha6)) {
out.writeOptionalVInt(version);
}
}
public static class Builder {
@ -220,6 +264,8 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
private int order;
private Integer version;
private String template;
private Settings settings = Settings.Builder.EMPTY_SETTINGS;
@ -240,6 +286,7 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
public Builder(IndexTemplateMetaData indexTemplateMetaData) {
this.name = indexTemplateMetaData.name();
order(indexTemplateMetaData.order());
version(indexTemplateMetaData.version());
template(indexTemplateMetaData.template());
settings(indexTemplateMetaData.settings());
@ -253,6 +300,11 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
return this;
}
public Builder version(Integer version) {
this.version = version;
return this;
}
public Builder template(String template) {
this.template = template;
return this;
@ -312,14 +364,18 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
}
public IndexTemplateMetaData build() {
return new IndexTemplateMetaData(name, order, template, settings, mappings.build(), aliases.build(), customs.build());
return new IndexTemplateMetaData(name, order, version, template, settings, mappings.build(), aliases.build(), customs.build());
}
@SuppressWarnings("unchecked")
public static void toXContent(IndexTemplateMetaData indexTemplateMetaData, XContentBuilder builder, ToXContent.Params params) throws IOException {
public static void toXContent(IndexTemplateMetaData indexTemplateMetaData, XContentBuilder builder, ToXContent.Params params)
throws IOException {
builder.startObject(indexTemplateMetaData.name());
builder.field("order", indexTemplateMetaData.order());
if (indexTemplateMetaData.version() != null) {
builder.field("version", indexTemplateMetaData.version());
}
builder.field("template", indexTemplateMetaData.template());
builder.startObject("settings");
@ -380,7 +436,9 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
} else if (token == XContentParser.Token.START_OBJECT) {
if ("settings".equals(currentFieldName)) {
Settings.Builder templateSettingsBuilder = Settings.builder();
templateSettingsBuilder.put(SettingsLoader.Helper.loadNestedFromMap(parser.mapOrdered())).normalizePrefix(IndexMetaData.INDEX_SETTING_PREFIX);
templateSettingsBuilder.put(
SettingsLoader.Helper.loadNestedFromMap(parser.mapOrdered()))
.normalizePrefix(IndexMetaData.INDEX_SETTING_PREFIX);
builder.settings(templateSettingsBuilder.build());
} else if ("mappings".equals(currentFieldName)) {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
@ -388,7 +446,8 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
String mappingType = currentFieldName;
Map<String, Object> mappingSource = MapBuilder.<String, Object>newMapBuilder().put(mappingType, parser.mapOrdered()).map();
Map<String, Object> mappingSource =
MapBuilder.<String, Object>newMapBuilder().put(mappingType, parser.mapOrdered()).map();
builder.putMapping(mappingType, XContentFactory.jsonBuilder().map(mappingSource).string());
}
}
@ -428,6 +487,8 @@ public class IndexTemplateMetaData extends AbstractDiffable<IndexTemplateMetaDat
builder.template(parser.text());
} else if ("order".equals(currentFieldName)) {
builder.order(parser.intValue());
} else if ("version".equals(currentFieldName)) {
builder.version(parser.intValue());
}
}
}

View File

@ -204,6 +204,7 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
createdIndex = dummyIndexService.index();
templateBuilder.order(request.order);
templateBuilder.version(request.version);
templateBuilder.template(request.template);
templateBuilder.settings(request.settings);
@ -288,6 +289,7 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
final String cause;
boolean create;
int order;
Integer version;
String template;
Settings settings = Settings.Builder.EMPTY_SETTINGS;
Map<String, String> mappings = new HashMap<>();
@ -345,6 +347,11 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
this.masterTimeout = masterTimeout;
return this;
}
public PutRequest version(Integer version) {
this.version = version;
return this;
}
}
public static class PutResponse {

View File

@ -19,7 +19,6 @@
package org.elasticsearch.rest.action.admin.indices;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateResponse;
import org.elasticsearch.client.node.NodeClient;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
@ -41,7 +40,6 @@ public class RestPutIndexTemplateAction extends BaseRestHandler {
controller.registerHandler(RestRequest.Method.POST, "/_template/{name}", this);
}
@SuppressWarnings({"unchecked"})
@Override
public void handleRequest(final RestRequest request, final RestChannel channel, final NodeClient client) {
PutIndexTemplateRequest putRequest = new PutIndexTemplateRequest(request.param("name"));
@ -51,6 +49,6 @@ public class RestPutIndexTemplateAction extends BaseRestHandler {
putRequest.create(request.paramAsBoolean("create", false));
putRequest.cause(request.param("cause", ""));
putRequest.source(request.content());
client.admin().indices().putTemplate(putRequest, new AcknowledgedRestListener<PutIndexTemplateResponse>(channel));
client.admin().indices().putTemplate(putRequest, new AcknowledgedRestListener<>(channel));
}
}

View File

@ -27,20 +27,20 @@ import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateReque
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.ParsingException;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.elasticsearch.indices.IndexTemplateAlreadyExistsException;
import org.elasticsearch.indices.InvalidAliasNameException;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.test.ESIntegTestCase;
import java.io.IOException;
import org.junit.After;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
@ -68,6 +68,11 @@ import static org.hamcrest.Matchers.nullValue;
*/
public class SimpleIndexTemplateIT extends ESIntegTestCase {
@After
public void cleanupTemplates() {
client().admin().indices().prepareDeleteTemplate("*").get();
}
public void testSimpleIndexTemplateTests() throws Exception {
// clean all templates setup by the framework.
client().admin().indices().prepareDeleteTemplate("*").get();
@ -113,7 +118,9 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
// index something into test_index, will match on both templates
client().prepareIndex("test_index", "type1", "1").setSource("field1", "value1", "field2", "value 2").setRefreshPolicy(IMMEDIATE).get();
client().prepareIndex("test_index", "type1", "1")
.setSource("field1", "value1", "field2", "value 2")
.setRefreshPolicy(IMMEDIATE).get();
ensureGreen();
SearchResponse searchResponse = client().prepareSearch("test_index")
@ -126,7 +133,9 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
// field2 is not stored.
assertThat(searchResponse.getHits().getAt(0).field("field2"), nullValue());
client().prepareIndex("text_index", "type1", "1").setSource("field1", "value1", "field2", "value 2").setRefreshPolicy(IMMEDIATE).get();
client().prepareIndex("text_index", "type1", "1")
.setSource("field1", "value1", "field2", "value 2")
.setRefreshPolicy(IMMEDIATE).get();
ensureGreen();
// now only match on one template (template_1)
@ -164,9 +173,12 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
logger.info("--> explicitly delete template_1");
admin().indices().prepareDeleteTemplate("template_1").execute().actionGet();
assertThat(admin().cluster().prepareState().execute().actionGet().getState().metaData().templates().size(), equalTo(1 + existingTemplates));
assertThat(admin().cluster().prepareState().execute().actionGet().getState().metaData().templates().containsKey("template_2"), equalTo(true));
assertThat(admin().cluster().prepareState().execute().actionGet().getState().metaData().templates().containsKey("template_1"), equalTo(false));
ClusterState state = admin().cluster().prepareState().execute().actionGet().getState();
assertThat(state.metaData().templates().size(), equalTo(1 + existingTemplates));
assertThat(state.metaData().templates().containsKey("template_2"), equalTo(true));
assertThat(state.metaData().templates().containsKey("template_1"), equalTo(false));
logger.info("--> put template_1 back");
@ -181,11 +193,13 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
logger.info("--> delete template*");
admin().indices().prepareDeleteTemplate("template*").execute().actionGet();
assertThat(admin().cluster().prepareState().execute().actionGet().getState().metaData().templates().size(), equalTo(existingTemplates));
assertThat(admin().cluster().prepareState().execute().actionGet().getState().metaData().templates().size(),
equalTo(existingTemplates));
logger.info("--> delete * with no templates, make sure we don't get a failure");
admin().indices().prepareDeleteTemplate("*").execute().actionGet();
assertThat(admin().cluster().prepareState().execute().actionGet().getState().metaData().templates().size(), equalTo(0));
assertThat(admin().cluster().prepareState().execute().actionGet().getState().metaData().templates().size(),
equalTo(0));
}
public void testThatGetIndexTemplatesWorks() throws Exception {
@ -193,6 +207,7 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
client().admin().indices().preparePutTemplate("template_1")
.setTemplate("te*")
.setOrder(0)
.setVersion(123)
.addMapping("type1", XContentFactory.jsonBuilder().startObject().startObject("type1").startObject("properties")
.startObject("field1").field("type", "text").field("store", true).endObject()
.startObject("field2").field("type", "keyword").field("store", true).endObject()
@ -205,9 +220,11 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
assertThat(getTemplate1Response.getIndexTemplates().get(0), is(notNullValue()));
assertThat(getTemplate1Response.getIndexTemplates().get(0).getTemplate(), is("te*"));
assertThat(getTemplate1Response.getIndexTemplates().get(0).getOrder(), is(0));
assertThat(getTemplate1Response.getIndexTemplates().get(0).getVersion(), is(123));
logger.info("--> get non-existing-template");
GetIndexTemplatesResponse getTemplate2Response = client().admin().indices().prepareGetTemplates("non-existing-template").execute().actionGet();
GetIndexTemplatesResponse getTemplate2Response =
client().admin().indices().prepareGetTemplates("non-existing-template").execute().actionGet();
assertThat(getTemplate2Response.getIndexTemplates(), hasSize(0));
}
@ -348,7 +365,8 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
.filter(QueryBuilders.termsQuery("_type", "typeX", "typeY", "typeZ")))
.get();
assertAcked(prepareCreate("test_index").addMapping("type1").addMapping("type2").addMapping("typeX").addMapping("typeY").addMapping("typeZ"));
assertAcked(prepareCreate("test_index")
.addMapping("type1").addMapping("type2").addMapping("typeX").addMapping("typeY").addMapping("typeZ"));
ensureGreen();
client().prepareIndex("test_index", "type1", "1").setSource("field", "A value").get();
@ -582,7 +600,8 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
.setOrder(0)
.addMapping("test", "field", "type=text")
.addAlias(new Alias("alias1").filter(termQuery("field", "value"))).get();
// Indexing into b should succeed, because the field mapping for field 'field' is defined in the _default_ mapping and the test type exists.
// Indexing into b should succeed, because the field mapping for field 'field' is defined in the _default_ mapping and
// the test type exists.
client().admin().indices().preparePutTemplate("template2")
.setTemplate("b*")
.setOrder(0)
@ -688,5 +707,21 @@ public class SimpleIndexTemplateIT extends ESIntegTestCase {
}
public void testOrderAndVersion() {
int order = randomInt();
Integer version = randomBoolean() ? randomInt() : null;
assertAcked(client().admin().indices().preparePutTemplate("versioned_template")
.setTemplate("te*")
.setVersion(version)
.setOrder(order)
.addMapping("test", "field", "type=text")
.get());
GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates("versioned_template").get();
assertThat(response.getIndexTemplates().size(), equalTo(1));
assertThat(response.getIndexTemplates().get(0).getVersion(), equalTo(version));
assertThat(response.getIndexTemplates().get(0).getOrder(), equalTo(order));
}
}

View File

@ -70,3 +70,99 @@
settings:
number_of_shards: 1
number_of_replicas: 0
---
"Test Put Versioned Template":
- do:
indices.put_template:
id: "my_template"
body: >
{
"version": 10,
"template": "*",
"settings": { "number_of_shards": 1 }
}
- match: { acknowledged: true }
- do:
indices.get_template:
id: "my_template"
- match: { my_template.version: 10 }
# Lower version
- do:
indices.put_template:
id: "my_template"
body: >
{
"version": 9,
"template": "*",
"settings": { "number_of_shards": 1 }
}
- match: { acknowledged: true }
- do:
indices.get_template:
id: "my_template"
- match: { my_template.version: 9 }
# Higher version
- do:
indices.put_template:
id: "my_template"
body: >
{
"version": 6789,
"template": "*",
"settings": { "number_of_shards": 1 }
}
- match: { acknowledged: true }
- do:
indices.get_template:
id: "my_template"
- match: { my_template.version: 6789 }
# No version
- do:
indices.put_template:
id: "my_template"
body: >
{
"template": "*",
"settings": { "number_of_shards": 1 }
}
- match: { acknowledged: true }
- do:
indices.get_template:
id: "my_template"
- is_false: my_template.version
# Coming back with a version
- do:
indices.put_template:
id: "my_template"
body: >
{
"version": 5385,
"template": "*",
"settings": { "number_of_shards": 1 }
}
- match: { acknowledged: true }
- do:
indices.get_template:
id: "my_template"
- match: { my_template.version: 5385 }
# Able to delete the versioned template
- do:
indices.delete_template:
id: "my_template"
- match: { acknowledged: true }
- do:
catch: missing
indices.get_template:
id: "my_template"