Added support for aliases to index templates

Adapted existing PR (#2739) to updated code (post #4920), added tests and docs (@javanna)

Closes #1825
This commit is contained in:
James Brook 2013-11-26 12:32:25 +01:00 committed by Luca Cavanna
parent 56fa5458e6
commit a93d6d55a5
19 changed files with 684 additions and 21 deletions

View File

@ -27,6 +27,35 @@ Defines a template named template_1, with a template pattern of `te*`.
The settings and mappings will be applied to any index name that matches
the `te*` template.
coming[1.1.0]
It is also possible to include aliases in an index template as follows:
[source,js]
--------------------------------------------------
curl -XPUT localhost:9200/_template/template_1 -d '
{
"template" : "te*",
"settings" : {
"number_of_shards" : 1
},
"aliases" : {
"alias1" : {},
"alias2" : {
"filter" : {
"term" : {"user" : "kimchy" }
},
"routing" : "kimchy"
},
"{index}-alias" : {} <1>
}
}
'
--------------------------------------------------
<1> the `{index}` placeholder within the alias name will be replaced with the
actual index name that the template gets applied to during index creation.
[float]
[[delete]]
=== Deleting a Template

View File

@ -15,3 +15,27 @@
- match: {test.template: "test-*"}
- match: {test.settings: {index.number_of_shards: '1', index.number_of_replicas: '0'}}
---
"Put template with aliases":
- do:
indices.put_template:
name: test
body:
template: test-*
aliases:
test_alias: {}
test_blias: {routing: b}
test_clias: {filter: {term :{user : kimchy}}}
- do:
indices.get_template:
name: test
- match: {test.template: "test-*"}
- length: {test.aliases: 3}
- is_true: test.aliases.test_alias
- match: {test.aliases.test_blias.index_routing: "b"}
- match: {test.aliases.test_blias.search_routing: "b"}
- match: {test.aliases.test_clias.filter.term.user: "kimchy"}

View File

@ -21,24 +21,27 @@ package org.elasticsearch.action.admin.indices.template.put;
import org.elasticsearch.ElasticsearchGenerationException;
import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.support.master.MasterNodeOperationRequest;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.bytes.BytesArray;
import org.elasticsearch.common.bytes.BytesReference;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.common.xcontent.*;
import org.elasticsearch.common.xcontent.support.XContentMapValues;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import static com.google.common.collect.Maps.newHashMap;
import static com.google.common.collect.Sets.newHashSet;
import static org.elasticsearch.action.ValidateActions.addValidationError;
import static org.elasticsearch.common.settings.ImmutableSettings.Builder.EMPTY_SETTINGS;
import static org.elasticsearch.common.settings.ImmutableSettings.readSettingsFromStream;
@ -63,6 +66,8 @@ public class PutIndexTemplateRequest extends MasterNodeOperationRequest<PutIndex
private Map<String, String> mappings = newHashMap();
private final Set<Alias> aliases = newHashSet();
private Map<String, IndexMetaData.Custom> customs = newHashMap();
PutIndexTemplateRequest() {
@ -251,6 +256,7 @@ public class PutIndexTemplateRequest extends MasterNodeOperationRequest<PutIndex
/**
* The template source definition.
*/
@SuppressWarnings("unchecked")
public PutIndexTemplateRequest source(Map templateSource) {
Map<String, Object> source = templateSource;
for (Map.Entry<String, Object> entry : source.entrySet()) {
@ -272,6 +278,8 @@ public class PutIndexTemplateRequest extends MasterNodeOperationRequest<PutIndex
}
mapping(entry1.getKey(), (Map<String, Object>) entry1.getValue());
}
} else if (name.equals("aliases")) {
aliases((Map<String, Object>) entry.getValue());
} else {
// maybe custom?
IndexMetaData.Custom.Factory factory = IndexMetaData.lookupFactory(name);
@ -336,6 +344,66 @@ public class PutIndexTemplateRequest extends MasterNodeOperationRequest<PutIndex
return this.customs;
}
Set<Alias> aliases() {
return this.aliases;
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
@SuppressWarnings("unchecked")
public PutIndexTemplateRequest aliases(Map source) {
try {
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.map(source);
return aliases(builder.bytes());
} catch (IOException e) {
throw new ElasticsearchGenerationException("Failed to generate [" + source + "]", e);
}
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
public PutIndexTemplateRequest aliases(XContentBuilder source) {
return aliases(source.bytes());
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
public PutIndexTemplateRequest aliases(String source) {
return aliases(new BytesArray(source));
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
public PutIndexTemplateRequest aliases(BytesReference source) {
try {
XContentParser parser = XContentHelper.createParser(source);
//move to the first alias
parser.nextToken();
while ((parser.nextToken()) != XContentParser.Token.END_OBJECT) {
alias(Alias.fromXContent(parser));
}
return this;
} catch(IOException e) {
throw new ElasticsearchParseException("Failed to parse aliases", e);
}
}
/**
* Adds an alias that will be added when the index gets created.
*
* @param alias The metadata for the new alias
* @return the index template creation request
*/
public PutIndexTemplateRequest alias(Alias alias) {
aliases.add(alias);
return this;
}
@Override
public void readFrom(StreamInput in) throws IOException {
super.readFrom(in);
@ -355,6 +423,12 @@ public class PutIndexTemplateRequest extends MasterNodeOperationRequest<PutIndex
IndexMetaData.Custom customIndexMetaData = IndexMetaData.lookupFactorySafe(type).readFrom(in);
customs.put(type, customIndexMetaData);
}
if (in.getVersion().onOrAfter(Version.V_1_1_0)) {
int aliasesSize = in.readVInt();
for (int i = 0; i < aliasesSize; i++) {
aliases.add(Alias.read(in));
}
}
}
@Override
@ -376,5 +450,11 @@ public class PutIndexTemplateRequest extends MasterNodeOperationRequest<PutIndex
out.writeString(entry.getKey());
IndexMetaData.lookupFactorySafe(entry.getKey()).writeTo(entry.getValue(), out);
}
if (out.getVersion().onOrAfter(Version.V_1_1_0)) {
out.writeVInt(aliases.size());
for (Alias alias : aliases) {
alias.writeTo(out);
}
}
}
}

View File

@ -19,6 +19,7 @@
package org.elasticsearch.action.admin.indices.template.put;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.client.internal.InternalIndicesAdminClient;
@ -109,6 +110,49 @@ public class PutIndexTemplateRequestBuilder extends MasterNodeOperationRequestBu
return this;
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
public PutIndexTemplateRequestBuilder setAliases(Map source) {
request.aliases(source);
return this;
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
public PutIndexTemplateRequestBuilder setAliases(String source) {
request.aliases(source);
return this;
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
public PutIndexTemplateRequestBuilder setAliases(XContentBuilder source) {
request.aliases(source);
return this;
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
public PutIndexTemplateRequestBuilder setAliases(BytesReference source) {
request.aliases(source);
return this;
}
/**
* Adds an alias that will be added when the index template gets created.
*
* @param alias The alias
* @return the request builder
*/
public PutIndexTemplateRequestBuilder addAlias(Alias alias) {
request.alias(alias);
return this;
}
/**
* The cause for this index template creation.
*/

View File

@ -83,6 +83,7 @@ public class TransportPutIndexTemplateAction extends TransportMasterNodeOperatio
.order(request.order())
.settings(request.settings())
.mappings(request.mappings())
.aliases(request.aliases())
.customs(request.customs())
.create(request.create())
.masterTimeout(request.masterNodeTimeout()),

View File

@ -62,6 +62,10 @@ public class AliasMetaData {
}
}
private AliasMetaData(AliasMetaData aliasMetaData, String alias) {
this(alias, aliasMetaData.filter(), aliasMetaData.indexRouting(), aliasMetaData.searchRouting());
}
public String alias() {
return alias;
}
@ -110,6 +114,13 @@ public class AliasMetaData {
return new Builder(alias);
}
/**
* Creates a new AliasMetaData instance with same content as the given one, but with a different alias name
*/
public static AliasMetaData newAliasMetaData(AliasMetaData aliasMetaData, String newAlias) {
return new AliasMetaData(aliasMetaData, newAlias);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
@ -137,7 +148,7 @@ public class AliasMetaData {
public static class Builder {
private String alias;
private final String alias;
private CompressedString filter;

View File

@ -31,6 +31,8 @@ import org.elasticsearch.index.Index;
import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.indices.InvalidAliasNameException;
import java.io.IOException;
/**
* Validator for an alias, to be used before adding an alias to the index metadata
* and make sure the alias is valid
@ -44,7 +46,8 @@ public class AliasValidator extends AbstractComponent {
/**
* Allows to validate an {@link org.elasticsearch.cluster.metadata.AliasAction} and make sure
* it's valid before it gets added to the index metadata
* it's valid before it gets added to the index metadata. Doesn't validate the alias filter.
* @throws org.elasticsearch.ElasticsearchIllegalArgumentException if the alias is not valid
*/
public void validateAliasAction(AliasAction aliasAction, MetaData metaData) {
validateAlias(aliasAction.alias(), aliasAction.index(), aliasAction.indexRouting(), metaData);
@ -52,22 +55,58 @@ public class AliasValidator extends AbstractComponent {
/**
* Allows to validate an {@link org.elasticsearch.action.admin.indices.alias.Alias} and make sure
* it's valid before it gets added to the index metadata
* it's valid before it gets added to the index metadata. Doesn't validate the alias filter.
* @throws org.elasticsearch.ElasticsearchIllegalArgumentException if the alias is not valid
*/
public void validateAlias(Alias alias, String index, MetaData metaData) {
validateAlias(alias.name(), index, alias.indexRouting(), metaData);
}
private void validateAlias(String alias, String index, String indexRouting, MetaData metaData) {
assert metaData != null;
if (!Strings.hasText(alias) || !Strings.hasText(index)) {
throw new ElasticsearchIllegalArgumentException("index name and alias name are required");
/**
* Allows to validate an {@link org.elasticsearch.cluster.metadata.AliasMetaData} and make sure
* it's valid before it gets added to the index metadata. Doesn't validate the alias filter.
* @throws org.elasticsearch.ElasticsearchIllegalArgumentException if the alias is not valid
*/
public void validateAliasMetaData(AliasMetaData aliasMetaData, String index, MetaData metaData) {
validateAlias(aliasMetaData.alias(), index, aliasMetaData.indexRouting(), metaData);
}
/**
* Allows to partially validate an alias, without knowing which index it'll get applied to.
* Useful with index templates containing aliases. Checks also that it is possible to parse
* the alias filter via {@link org.elasticsearch.common.xcontent.XContentParser},
* without validating it as a filter though.
* @throws org.elasticsearch.ElasticsearchIllegalArgumentException if the alias is not valid
*/
public void validateAliasStandalone(Alias alias) {
validateAliasStandalone(alias.name(), alias.indexRouting());
if (Strings.hasLength(alias.filter())) {
try {
XContentParser parser = XContentFactory.xContent(alias.filter()).createParser(alias.filter());
parser.mapAndClose();
} catch (Throwable e) {
throw new ElasticsearchIllegalArgumentException("failed to parse filter for alias [" + alias.name() + "]", e);
}
}
}
private void validateAlias(String alias, String index, String indexRouting, MetaData metaData) {
validateAliasStandalone(alias, indexRouting);
if (!Strings.hasText(index)) {
throw new ElasticsearchIllegalArgumentException("index name is required");
}
assert metaData != null;
if (metaData.hasIndex(alias)) {
throw new InvalidAliasNameException(new Index(index), alias, "an index exists with the same name as the alias");
}
}
private void validateAliasStandalone(String alias, String indexRouting) {
if (!Strings.hasText(alias)) {
throw new ElasticsearchIllegalArgumentException("alias name is required");
}
if (indexRouting != null && indexRouting.indexOf(',') != -1) {
throw new ElasticsearchIllegalArgumentException("alias [" + alias + "] has several index routing values associated with it");
}
@ -82,13 +121,32 @@ public class AliasValidator extends AbstractComponent {
assert indexQueryParserService != null;
try {
XContentParser parser = XContentFactory.xContent(filter).createParser(filter);
validateAliasFilter(parser, indexQueryParserService);
} catch (Throwable e) {
throw new ElasticsearchIllegalArgumentException("failed to parse filter for alias [" + alias + "]", e);
}
}
/**
* Validates an alias filter by parsing it using the
* provided {@link org.elasticsearch.index.query.IndexQueryParserService}
* @throws org.elasticsearch.ElasticsearchIllegalArgumentException if the filter is not valid
*/
public void validateAliasFilter(String alias, byte[] filter, IndexQueryParserService indexQueryParserService) {
assert indexQueryParserService != null;
try {
XContentParser parser = XContentFactory.xContent(filter).createParser(filter);
validateAliasFilter(parser, indexQueryParserService);
} catch (Throwable e) {
throw new ElasticsearchIllegalArgumentException("failed to parse filter for alias [" + alias + "]", e);
}
}
private void validateAliasFilter(XContentParser parser, IndexQueryParserService indexQueryParserService) throws IOException {
try {
indexQueryParserService.parseInnerFilter(parser);
} finally {
parser.close();
}
} catch (Throwable e) {
throw new ElasticsearchIllegalArgumentException("failed to parse filter for alias [" + alias + "]", e);
}
}
}

View File

@ -18,8 +18,10 @@
*/
package org.elasticsearch.cluster.metadata;
import com.carrotsearch.hppc.cursors.ObjectCursor;
import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
import com.google.common.collect.Sets;
import org.elasticsearch.Version;
import org.elasticsearch.common.collect.ImmutableOpenMap;
import org.elasticsearch.common.collect.MapBuilder;
import org.elasticsearch.common.compress.CompressedString;
@ -53,14 +55,18 @@ public class IndexTemplateMetaData {
// the mapping source should always include the type as top level
private final ImmutableOpenMap<String, CompressedString> mappings;
private final ImmutableOpenMap<String, AliasMetaData> aliases;
private final ImmutableOpenMap<String, IndexMetaData.Custom> customs;
public IndexTemplateMetaData(String name, int order, String template, Settings settings, ImmutableOpenMap<String, CompressedString> mappings, ImmutableOpenMap<String, IndexMetaData.Custom> customs) {
public IndexTemplateMetaData(String name, int order, String template, Settings settings, ImmutableOpenMap<String, CompressedString> mappings,
ImmutableOpenMap<String, AliasMetaData> aliases, ImmutableOpenMap<String, IndexMetaData.Custom> customs) {
this.name = name;
this.order = order;
this.template = template;
this.settings = settings;
this.mappings = mappings;
this.aliases = aliases;
this.customs = customs;
}
@ -104,6 +110,14 @@ public class IndexTemplateMetaData {
return this.mappings;
}
public ImmutableOpenMap<String, AliasMetaData> aliases() {
return this.aliases;
}
public ImmutableOpenMap<String, AliasMetaData> getAliases() {
return this.aliases;
}
public ImmutableOpenMap<String, IndexMetaData.Custom> customs() {
return this.customs;
}
@ -112,6 +126,7 @@ public class IndexTemplateMetaData {
return this.customs;
}
@SuppressWarnings("unchecked")
public <T extends IndexMetaData.Custom> T custom(String type) {
return (T) customs.get(type);
}
@ -163,11 +178,14 @@ public class IndexTemplateMetaData {
private final ImmutableOpenMap.Builder<String, CompressedString> mappings;
private final ImmutableOpenMap.Builder<String, AliasMetaData> aliases;
private final ImmutableOpenMap.Builder<String, IndexMetaData.Custom> customs;
public Builder(String name) {
this.name = name;
mappings = ImmutableOpenMap.builder();
aliases = ImmutableOpenMap.builder();
customs = ImmutableOpenMap.builder();
}
@ -178,6 +196,7 @@ public class IndexTemplateMetaData {
settings(indexTemplateMetaData.settings());
mappings = ImmutableOpenMap.builder(indexTemplateMetaData.mappings());
aliases = ImmutableOpenMap.builder(indexTemplateMetaData.aliases());
customs = ImmutableOpenMap.builder(indexTemplateMetaData.customs());
}
@ -220,6 +239,16 @@ public class IndexTemplateMetaData {
return this;
}
public Builder putAlias(AliasMetaData aliasMetaData) {
aliases.put(aliasMetaData.alias(), aliasMetaData);
return this;
}
public Builder putAlias(AliasMetaData.Builder aliasMetaData) {
aliases.put(aliasMetaData.alias(), aliasMetaData.build());
return this;
}
public Builder putCustom(String type, IndexMetaData.Custom customIndexMetaData) {
this.customs.put(type, customIndexMetaData);
return this;
@ -235,9 +264,10 @@ public class IndexTemplateMetaData {
}
public IndexTemplateMetaData build() {
return new IndexTemplateMetaData(name, order, template, settings, mappings.build(), customs.build());
return new IndexTemplateMetaData(name, order, template, settings, mappings.build(), aliases.build(), customs.build());
}
@SuppressWarnings("unchecked")
public static void toXContent(IndexTemplateMetaData indexTemplateMetaData, XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject(indexTemplateMetaData.name(), XContentBuilder.FieldCaseConversion.NONE);
@ -281,6 +311,12 @@ public class IndexTemplateMetaData {
builder.endObject();
}
builder.startObject("aliases");
for (ObjectCursor<AliasMetaData> cursor : indexTemplateMetaData.aliases().values()) {
AliasMetaData.Builder.toXContent(cursor.value, builder, params);
}
builder.endObject();
builder.endObject();
}
@ -313,6 +349,10 @@ public class IndexTemplateMetaData {
builder.putMapping(mappingType, XContentFactory.jsonBuilder().map(mappingSource).string());
}
}
} else if ("aliases".equals(currentFieldName)) {
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
builder.putAlias(AliasMetaData.Builder.fromXContent(parser));
}
} else {
// check if its a custom index metadata
IndexMetaData.Custom.Factory<IndexMetaData.Custom> factory = IndexMetaData.lookupFactory(currentFieldName);
@ -377,6 +417,13 @@ public class IndexTemplateMetaData {
for (int i = 0; i < mappingsSize; i++) {
builder.putMapping(in.readString(), CompressedString.readCompressedString(in));
}
if (in.getVersion().onOrAfter(Version.V_1_1_0)) {
int aliasesSize = in.readVInt();
for (int i = 0; i < aliasesSize; i++) {
AliasMetaData aliasMd = AliasMetaData.Builder.readFrom(in);
builder.putAlias(aliasMd);
}
}
int customSize = in.readVInt();
for (int i = 0; i < customSize; i++) {
String type = in.readString();
@ -396,6 +443,12 @@ public class IndexTemplateMetaData {
out.writeString(cursor.key);
cursor.value.writeTo(out);
}
if (out.getVersion().onOrAfter(Version.V_1_1_0)) {
out.writeVInt(indexTemplateMetaData.aliases().size());
for (ObjectCursor<AliasMetaData> cursor : indexTemplateMetaData.aliases().values()) {
AliasMetaData.Builder.writeTo(cursor.value, out);
}
}
out.writeVInt(indexTemplateMetaData.customs().size());
for (ObjectObjectCursor<String, IndexMetaData.Custom> cursor : indexTemplateMetaData.customs()) {
out.writeString(cursor.key);

View File

@ -232,6 +232,8 @@ public class MetaDataCreateIndexService extends AbstractComponent {
// add the request mapping
Map<String, Map<String, Object>> mappings = Maps.newHashMap();
Map<String, AliasMetaData> templatesAliases = Maps.newHashMap();
for (Map.Entry<String, String> entry : request.mappings().entrySet()) {
mappings.put(entry.getKey(), parseMapping(entry.getValue()));
}
@ -261,6 +263,28 @@ public class MetaDataCreateIndexService extends AbstractComponent {
customs.put(type, merged);
}
}
//handle aliases
for (ObjectObjectCursor<String, AliasMetaData> cursor : template.aliases()) {
AliasMetaData aliasMetaData = cursor.value;
//if an alias with same name came with the create index request itself,
// ignore this one taken from the index template
if (request.aliases().contains(new Alias(aliasMetaData.alias()))) {
continue;
}
//if an alias with same name was already processed, ignore this one
if (templatesAliases.containsKey(cursor.key)) {
continue;
}
//Allow templatesAliases to be templated by replacing a token with the name of the index that we are applying it to
if (aliasMetaData.alias().contains("{index}")) {
String templatedAlias = aliasMetaData.alias().replace("{index}", request.index());
aliasMetaData = AliasMetaData.newAliasMetaData(aliasMetaData, templatedAlias);
}
aliasValidator.validateAliasMetaData(aliasMetaData, request.index(), currentState.metaData());
templatesAliases.put(aliasMetaData.alias(), aliasMetaData);
}
}
// now add config level mappings
@ -349,6 +373,11 @@ public class MetaDataCreateIndexService extends AbstractComponent {
aliasValidator.validateAliasFilter(alias.name(), alias.filter(), indexQueryParserService);
}
}
for (AliasMetaData aliasMetaData : templatesAliases.values()) {
if (aliasMetaData.filter() != null) {
aliasValidator.validateAliasFilter(aliasMetaData.alias(), aliasMetaData.filter().uncompressed(), indexQueryParserService);
}
}
// now, update the mappings with the actual source
Map<String, MappingMetaData> mappingsMetaData = Maps.newHashMap();
@ -361,15 +390,22 @@ public class MetaDataCreateIndexService extends AbstractComponent {
for (MappingMetaData mappingMd : mappingsMetaData.values()) {
indexMetaDataBuilder.putMapping(mappingMd);
}
for (AliasMetaData aliasMetaData : templatesAliases.values()) {
indexMetaDataBuilder.putAlias(aliasMetaData);
}
for (Alias alias : request.aliases()) {
AliasMetaData aliasMetaData = AliasMetaData.builder(alias.name()).filter(alias.filter())
.indexRouting(alias.indexRouting()).searchRouting(alias.searchRouting()).build();
indexMetaDataBuilder.putAlias(aliasMetaData);
}
for (Map.Entry<String, Custom> customEntry : customs.entrySet()) {
indexMetaDataBuilder.putCustom(customEntry.getKey(), customEntry.getValue());
}
indexMetaDataBuilder.state(request.state());
final IndexMetaData indexMetaData;
try {
indexMetaData = indexMetaDataBuilder.build();

View File

@ -19,10 +19,12 @@
package org.elasticsearch.cluster.metadata;
import com.carrotsearch.hppc.cursors.ObjectCursor;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.support.master.MasterNodeOperationRequest;
import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.ClusterState;
@ -39,21 +41,24 @@ import org.elasticsearch.indices.IndexTemplateAlreadyExistsException;
import org.elasticsearch.indices.IndexTemplateMissingException;
import org.elasticsearch.indices.InvalidIndexTemplateException;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/**
*
* Service responsible for submitting index templates updates
*/
public class MetaDataIndexTemplateService extends AbstractComponent {
private final ClusterService clusterService;
private final AliasValidator aliasValidator;
@Inject
public MetaDataIndexTemplateService(Settings settings, ClusterService clusterService) {
public MetaDataIndexTemplateService(Settings settings, ClusterService clusterService, AliasValidator aliasValidator) {
super(settings);
this.clusterService = clusterService;
this.aliasValidator = aliasValidator;
}
public void removeTemplates(final RemoveRequest request, final RemoveListener listener) {
@ -136,6 +141,11 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
for (Map.Entry<String, String> entry : request.mappings.entrySet()) {
templateBuilder.putMapping(entry.getKey(), entry.getValue());
}
for (Alias alias : request.aliases) {
AliasMetaData aliasMetaData = AliasMetaData.builder(alias.name()).filter(alias.filter())
.indexRouting(alias.indexRouting()).searchRouting(alias.searchRouting()).build();
templateBuilder.putAlias(aliasMetaData);
}
for (Map.Entry<String, IndexMetaData.Custom> entry : request.customs.entrySet()) {
templateBuilder.putCustom(entry.getKey(), entry.getValue());
}
@ -205,6 +215,11 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
if (!Strings.validFileNameExcludingAstrix(request.template)) {
throw new InvalidIndexTemplateException(request.name, "template must not container the following characters " + Strings.INVALID_FILENAME_CHARS);
}
for (Alias alias : request.aliases) {
//we validate the alias only partially, as we don't know yet to which index it'll get applied to
aliasValidator.validateAliasStandalone(alias);
}
}
public static interface PutListener {
@ -222,6 +237,7 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
String template;
Settings settings = ImmutableSettings.Builder.EMPTY_SETTINGS;
Map<String, String> mappings = Maps.newHashMap();
List<Alias> aliases = Lists.newArrayList();
Map<String, IndexMetaData.Custom> customs = Maps.newHashMap();
TimeValue masterTimeout = MasterNodeOperationRequest.DEFAULT_MASTER_NODE_TIMEOUT;
@ -256,6 +272,11 @@ public class MetaDataIndexTemplateService extends AbstractComponent {
return this;
}
public PutRequest aliases(Set<Alias> aliases) {
this.aliases.addAll(aliases);
return this;
}
public PutRequest customs(Map<String, IndexMetaData.Custom> customs) {
this.customs.putAll(customs);
return this;

View File

@ -92,9 +92,13 @@ public class ToAndFromJsonMetaDataTests extends ElasticsearchTestCase {
.putAlias(newAliasMetaDataBuilder("alias4").filter(ALIAS_FILTER2)))
.put(IndexTemplateMetaData.builder("foo")
.template("bar")
.order(1).settings(settingsBuilder()
.order(1)
.settings(settingsBuilder()
.put("setting1", "value1")
.put("setting2", "value2")))
.put("setting2", "value2"))
.putAlias(newAliasMetaDataBuilder("alias-bar1"))
.putAlias(newAliasMetaDataBuilder("alias-bar2").filter("{\"term\":{\"user\":\"kimchy\"}}"))
.putAlias(newAliasMetaDataBuilder("alias-bar3").routing("routing-bar")))
.build();
String metaDataSource = MetaData.Builder.toXContent(metaData);
@ -184,6 +188,13 @@ public class ToAndFromJsonMetaDataTests extends ElasticsearchTestCase {
assertThat(parsedMetaData.templates().get("foo").template(), is("bar"));
assertThat(parsedMetaData.templates().get("foo").settings().get("index.setting1"), is("value1"));
assertThat(parsedMetaData.templates().get("foo").settings().getByPrefix("index.").get("setting2"), is("value2"));
assertThat(parsedMetaData.templates().get("foo").aliases().size(), equalTo(3));
assertThat(parsedMetaData.templates().get("foo").aliases().get("alias-bar1").alias(), equalTo("alias-bar1"));
assertThat(parsedMetaData.templates().get("foo").aliases().get("alias-bar2").alias(), equalTo("alias-bar2"));
assertThat(parsedMetaData.templates().get("foo").aliases().get("alias-bar2").filter().string(), equalTo("{\"term\":{\"user\":\"kimchy\"}}"));
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"));
}
private static final String MAPPING_SOURCE1 = "{\"mapping1\":{\"text1\":{\"type\":\"string\"}}}";

View File

@ -34,6 +34,7 @@ import java.io.File;
import java.util.HashSet;
import java.util.Set;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.is;
/**
@ -84,6 +85,9 @@ public class IndexTemplateFileLoadingTests extends ElasticsearchIntegrationTest
ClusterStateResponse stateResponse = client().admin().cluster().prepareState().setIndices(indexName).get();
assertThat(stateResponse.getState().getMetaData().indices().get(indexName).getNumberOfShards(), is(10));
assertThat(stateResponse.getState().getMetaData().indices().get(indexName).getNumberOfReplicas(), is(0));
assertThat(stateResponse.getState().getMetaData().indices().get(indexName).aliases().size(), equalTo(1));
String aliasName = indexName + "-alias";
assertThat(stateResponse.getState().getMetaData().indices().get(indexName).aliases().get(aliasName).alias(), equalTo(aliasName));
}
}
}

View File

@ -19,22 +19,34 @@
package org.elasticsearch.indices.template;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse;
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
import org.elasticsearch.action.admin.indices.template.get.GetIndexTemplatesResponse;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.common.Priority;
import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.index.query.QueryParsingException;
import org.elasticsearch.indices.IndexTemplateAlreadyExistsException;
import org.elasticsearch.indices.InvalidAliasNameException;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.test.ElasticsearchIntegrationTest;
import org.junit.Test;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertHitCount;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertThrows;
import static org.hamcrest.Matchers.*;
@ -322,4 +334,265 @@ public class SimpleIndexTemplateTests extends ElasticsearchIntegrationTest {
GetSettingsResponse getSettingsResponse = client().admin().indices().prepareGetSettings("test").get();
assertThat(getSettingsResponse.getIndexToSettings().get("test").getAsMap().get("index.does_not_exist"), equalTo("test"));
}
public void testIndexTemplateWithAliases() throws Exception {
client().admin().indices().preparePutTemplate("template_with_aliases")
.setTemplate("te*")
.addAlias(new Alias("simple_alias"))
.addAlias(new Alias("templated_alias-{index}"))
.addAlias(new Alias("filtered_alias").filter("{\"type\":{\"value\":\"type2\"}}"))
.addAlias(new Alias("complex_filtered_alias")
.filter(FilterBuilders.termsFilter("_type", "typeX", "typeY", "typeZ").execution("bool").cache(true)))
.get();
client().prepareIndex("test_index", "type1", "1").setSource("field", "A value").get();
client().prepareIndex("test_index", "type2", "2").setSource("field", "B value").get();
client().prepareIndex("test_index", "typeX", "3").setSource("field", "C value").get();
client().prepareIndex("test_index", "typeY", "4").setSource("field", "D value").get();
client().prepareIndex("test_index", "typeZ", "5").setSource("field", "E value").get();
GetAliasesResponse getAliasesResponse = client().admin().indices().prepareGetAliases().setIndices("test_index").get();
assertThat(getAliasesResponse.getAliases().size(), equalTo(1));
assertThat(getAliasesResponse.getAliases().get("test_index").size(), equalTo(4));
refresh();
SearchResponse searchResponse = client().prepareSearch("test_index").get();
assertHitCount(searchResponse, 5l);
searchResponse = client().prepareSearch("simple_alias").get();
assertHitCount(searchResponse, 5l);
searchResponse = client().prepareSearch("templated_alias-test_index").get();
assertHitCount(searchResponse, 5l);
searchResponse = client().prepareSearch("filtered_alias").get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).type(), equalTo("type2"));
// Search the complex filter alias
searchResponse = client().prepareSearch("complex_filtered_alias").get();
assertHitCount(searchResponse, 3l);
Set<String> types = Sets.newHashSet();
for (SearchHit searchHit : searchResponse.getHits().getHits()) {
types.add(searchHit.getType());
}
assertThat(types.size(), equalTo(3));
assertThat(types, containsInAnyOrder("typeX", "typeY", "typeZ"));
}
@Test
public void testIndexTemplateWithAliasesInSource() {
client().admin().indices().preparePutTemplate("template_1")
.setSource("{\n" +
" \"template\" : \"*\",\n" +
" \"aliases\" : {\n" +
" \"my_alias\" : {\n" +
" \"filter\" : {\n" +
" \"type\" : {\n" +
" \"value\" : \"type2\"\n" +
" }\n" +
" }\n" +
" }\n" +
" }\n" +
"}").get();
createIndex("test_index");
ensureGreen();
GetAliasesResponse getAliasesResponse = client().admin().indices().prepareGetAliases().setIndices("test_index").get();
assertThat(getAliasesResponse.getAliases().size(), equalTo(1));
assertThat(getAliasesResponse.getAliases().get("test_index").size(), equalTo(1));
client().prepareIndex("test_index", "type1", "1").setSource("field", "value1").get();
client().prepareIndex("test_index", "type2", "2").setSource("field", "value2").get();
refresh();
SearchResponse searchResponse = client().prepareSearch("test_index").get();
assertHitCount(searchResponse, 2l);
searchResponse = client().prepareSearch("my_alias").get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).type(), equalTo("type2"));
}
@Test
public void testIndexTemplateWithAliasesSource() {
client().admin().indices().preparePutTemplate("template_1")
.setTemplate("te*")
.setAliases(
" {\n" +
" \"alias1\" : {},\n" +
" \"alias2\" : {\n" +
" \"filter\" : {\n" +
" \"type\" : {\n" +
" \"value\" : \"type2\"\n" +
" }\n" +
" }\n" +
" },\n" +
" \"alias3\" : { \"routing\" : \"1\" }" +
" }\n").get();
createIndex("test_index");
ensureGreen();
GetAliasesResponse getAliasesResponse = client().admin().indices().prepareGetAliases().setIndices("test_index").get();
assertThat(getAliasesResponse.getAliases().size(), equalTo(1));
assertThat(getAliasesResponse.getAliases().get("test_index").size(), equalTo(3));
client().prepareIndex("test_index", "type1", "1").setSource("field", "value1").get();
client().prepareIndex("test_index", "type2", "2").setSource("field", "value2").get();
refresh();
SearchResponse searchResponse = client().prepareSearch("test_index").get();
assertHitCount(searchResponse, 2l);
searchResponse = client().prepareSearch("alias1").get();
assertHitCount(searchResponse, 2l);
searchResponse = client().prepareSearch("alias2").get();
assertHitCount(searchResponse, 1l);
assertThat(searchResponse.getHits().getAt(0).type(), equalTo("type2"));
}
@Test
public void testDuplicateAlias() throws Exception {
client().admin().indices().preparePutTemplate("template_1")
.setTemplate("te*")
.addAlias(new Alias("my_alias").filter(FilterBuilders.termFilter("field", "value1")))
.addAlias(new Alias("my_alias").filter(FilterBuilders.termFilter("field", "value2")))
.get();
GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates("template_1").get();
assertThat(response.getIndexTemplates().size(), equalTo(1));
assertThat(response.getIndexTemplates().get(0).getAliases().size(), equalTo(1));
assertThat(response.getIndexTemplates().get(0).getAliases().get("my_alias").filter().string(), containsString("\"value1\""));
}
@Test
public void testAliasInvalidFilterValidJson() throws Exception {
//invalid filter but valid json: put index template works fine, fails during index creation
client().admin().indices().preparePutTemplate("template_1")
.setTemplate("te*")
.addAlias(new Alias("invalid_alias").filter("{ \"invalid\": {} }")).get();
GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates("template_1").get();
assertThat(response.getIndexTemplates().size(), equalTo(1));
assertThat(response.getIndexTemplates().get(0).getAliases().size(), equalTo(1));
assertThat(response.getIndexTemplates().get(0).getAliases().get("invalid_alias").filter().string(), equalTo("{\"invalid\":{}}"));
try {
createIndex("test");
fail("index creation should have failed due to invalid alias filter in matching index template");
} catch(ElasticsearchIllegalArgumentException e) {
assertThat(e.getMessage(), equalTo("failed to parse filter for alias [invalid_alias]"));
assertThat(e.getCause(), instanceOf(QueryParsingException.class));
assertThat(e.getCause().getMessage(), equalTo("[test] No filter registered for [invalid]"));
}
}
@Test
public void testAliasInvalidFilterInvalidJson() throws Exception {
//invalid json: put index template fails
PutIndexTemplateRequestBuilder putIndexTemplateRequestBuilder = client().admin().indices().preparePutTemplate("template_1")
.setTemplate("te*")
.addAlias(new Alias("invalid_alias").filter("abcde"));
try {
putIndexTemplateRequestBuilder.get();
} catch(ElasticsearchIllegalArgumentException e) {
assertThat(e.getMessage(), equalTo("failed to parse filter for alias [invalid_alias]"));
}
GetIndexTemplatesResponse response = client().admin().indices().prepareGetTemplates("template_1").get();
assertThat(response.getIndexTemplates().size(), equalTo(0));
}
@Test
public void testAliasNameExistingIndex() throws Exception {
createIndex("index");
client().admin().indices().preparePutTemplate("template_1")
.setTemplate("te*")
.addAlias(new Alias("index")).get();
try {
createIndex("test");
fail("index creation should have failed due to alias with existing index name in mathching index template");
} catch(InvalidAliasNameException e) {
assertThat(e.getMessage(), equalTo("[test] Invalid alias name [index], an index exists with the same name as the alias"));
}
}
@Test
public void testAliasEmptyName() throws Exception {
PutIndexTemplateRequestBuilder putIndexTemplateRequestBuilder = client().admin().indices().preparePutTemplate("template_1")
.setTemplate("te*")
.addAlias(new Alias(" ").indexRouting("1,2,3"));
try {
putIndexTemplateRequestBuilder.get();
fail("put template should have failed due to alias with empty name");
} catch (ElasticsearchIllegalArgumentException e) {
assertThat(e.getMessage(), equalTo("alias name is required"));
}
}
@Test
public void testAliasWithMultipleIndexRoutings() throws Exception {
PutIndexTemplateRequestBuilder putIndexTemplateRequestBuilder = client().admin().indices().preparePutTemplate("template_1")
.setTemplate("te*")
.addAlias(new Alias("alias").indexRouting("1,2,3"));
try {
putIndexTemplateRequestBuilder.get();
fail("put template should have failed due to alias with multiple index routings");
} catch (ElasticsearchIllegalArgumentException e) {
assertThat(e.getMessage(), equalTo("alias [alias] has several index routing values associated with it"));
}
}
@Test
public void testMultipleAliasesPrecedence() throws Exception {
client().admin().indices().preparePutTemplate("template1")
.setTemplate("*")
.setOrder(0)
.addAlias(new Alias("alias1"))
.addAlias(new Alias("{index}-alias"))
.addAlias(new Alias("alias3").filter(FilterBuilders.missingFilter("test")))
.addAlias(new Alias("alias4")).get();
client().admin().indices().preparePutTemplate("template2")
.setTemplate("te*")
.setOrder(1)
.addAlias(new Alias("alias1").routing("test"))
.addAlias(new Alias("alias3")).get();
assertAcked(prepareCreate("test").addAlias(new Alias("test-alias").searchRouting("test-routing")));
ensureGreen();
GetAliasesResponse getAliasesResponse = client().admin().indices().prepareGetAliases().addIndices("test").get();
assertThat(getAliasesResponse.getAliases().get("test").size(), equalTo(4));
for (AliasMetaData aliasMetaData : getAliasesResponse.getAliases().get("test")) {
assertThat(aliasMetaData.alias(), anyOf(equalTo("alias1"), equalTo("test-alias"), equalTo("alias3"), equalTo("alias4")));
if ("alias1".equals(aliasMetaData.alias())) {
assertThat(aliasMetaData.indexRouting(), equalTo("test"));
assertThat(aliasMetaData.searchRouting(), equalTo("test"));
} else if ("alias3".equals(aliasMetaData.alias())) {
assertThat(aliasMetaData.filter(), nullValue());
} else if ("test-alias".equals(aliasMetaData.alias())) {
assertThat(aliasMetaData.indexRouting(), nullValue());
assertThat(aliasMetaData.searchRouting(), equalTo("test-routing"));
}
}
}
}

View File

@ -3,5 +3,8 @@
"settings" : {
"index.number_of_shards": 10,
"index.number_of_replicas": 0
},
"aliases" : {
"{index}-alias" : {}
}
}

View File

@ -3,5 +3,8 @@
"settings" : {
"number_of_shards": 10,
"number_of_replicas": 0
},
"aliases" : {
"{index}-alias" : {}
}
}

View File

@ -5,5 +5,8 @@
"number_of_shards": 10,
"number_of_replicas": 0
}
},
"aliases" : {
"{index}-alias" : {}
}
}

View File

@ -4,6 +4,9 @@
"settings" : {
"index.number_of_shards": 10,
"index.number_of_replicas": 0
},
"aliases" : {
"{index}-alias" : {}
}
}
}

View File

@ -4,6 +4,9 @@
"settings" : {
"number_of_shards": 10,
"number_of_replicas": 0
},
"aliases" : {
"{index}-alias" : {}
}
}
}

View File

@ -6,6 +6,9 @@
"number_of_shards": 10,
"number_of_replicas": 0
}
},
"aliases" : {
"{index}-alias" : {}
}
}
}