Added support for aliases to create index api

It is now possible to specify aliases during index creation:

curl -XPUT 'http://localhost:9200/test' -d '
{
    "aliases" : {
        "alias1" : {},
        "alias2" : {
            "filter" : { "term" : {"field":"value"}}
        }
    }
}'

Closes #4920
This commit is contained in:
Luca Cavanna 2014-01-28 10:47:40 +01:00
parent 8601529be8
commit 3afdf4a872
13 changed files with 647 additions and 59 deletions

View File

@ -185,7 +185,7 @@ Adding user alias:
[source,js]
--------------------------------------------------
curl -XPUT 'localhost:9200/users/_alias/user_12' -d '{
"routing" : "12",
"routing" : "12",
"filter" : {
"term" : {
"user_id" : 12
@ -194,6 +194,28 @@ curl -XPUT 'localhost:9200/users/_alias/user_12' -d '{
}'
--------------------------------------------------
[float]
[[alias-index-creation]]
=== Aliases during index creation
coming[1.1.0]
Aliases can also be specified during <<create-index-aliases,index creation>>:
[source,js]
--------------------------------------------------
curl -XPUT localhost:9200/logs_20142801 -d '{
"aliases" : {
"current_day" : {},
"2014" : {
"filter" : {
"term" : {"year" : 2014 }
}
}
}
}'
--------------------------------------------------
[float]
[[deleting]]
=== Delete aliases

View File

@ -103,3 +103,26 @@ curl -XPUT localhost:9200/test -d '{
}
}'
--------------------------------------------------
[float]
[[create-index-aliases]]
=== Aliases
coming[1.1.0]
The create index API allows also to provide a set of <<indices-aliases,aliases>>:
[source,js]
--------------------------------------------------
curl -XPUT localhost:9200/test -d '{
"aliases" : {
"alias_1" : {},
"alias_2" : {
"filter" : {
"term" : {"user" : "kimchy" }
},
"routing" : "kimchy"
}
}
}'
--------------------------------------------------

View File

@ -50,7 +50,35 @@
- match: {test_index.warmers.test_warmer.source.query.match_all: {}}
---
"Create index with mappings, settings and warmers":
"Create index with aliases":
- do:
indices.create:
index: test_index
body:
aliases:
test_alias: {}
test_blias:
routing: b
test_clias:
filter:
term:
field : value
- do:
indices.get_alias:
index: test_index
- match: {test_index.aliases.test_alias: {}}
- match: {test_index.aliases.test_blias.search_routing: b}
- match: {test_index.aliases.test_blias.index_routing: b}
- is_false: test_index.aliases.test_blias.filter
- match: {test_index.aliases.test_clias.filter.term.field: value}
- is_false: test_index.aliases.test_clias.index_routing
- is_false: test_index.aliases.test_clias.search_routing
---
"Create index with mappings, settings, warmers and aliases":
- do:
indices.create:
@ -65,6 +93,9 @@
source:
query:
match_all: {}
aliases:
test_alias: {}
test_blias: {routing: b}
- do:
indices.get_mapping:
@ -83,3 +114,11 @@
index: test_index
- match: { test_index.warmers.test_warmer.source.query.match_all: {}}
- do:
indices.get_alias:
index: test_index
- match: { test_index.aliases.test_alias: {}}
- match: { test_index.aliases.test_blias.search_routing: b}
- match: { test_index.aliases.test_blias.index_routing: b}

View File

@ -4,30 +4,18 @@ setup:
- do:
indices.create:
index: test_index
body:
aliases:
test_alias: {}
test_blias: {}
- do:
indices.create:
index: test_index_2
- do:
indices.put_alias:
index: test_index
name: test_alias
- do:
indices.put_alias:
index: test_index
name: test_blias
- do:
indices.put_alias:
index: test_index_2
name: test_alias
- do:
indices.put_alias:
index: test_index_2
name: test_blias
body:
aliases:
test_alias: {}
test_blias: {}
---
"Get all aliases via /_alias":

View File

@ -0,0 +1,234 @@
/*
* 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.action.admin.indices.alias;
import org.elasticsearch.ElasticsearchGenerationException;
import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.io.stream.Streamable;
import org.elasticsearch.common.xcontent.*;
import org.elasticsearch.index.query.FilterBuilder;
import java.io.IOException;
import java.util.Map;
/**
* Represents an alias, to be associated with an index
*/
public class Alias implements Streamable {
private String name;
@Nullable
private String filter;
@Nullable
private String indexRouting;
@Nullable
private String searchRouting;
private Alias() {
}
public Alias(String name) {
this.name = name;
}
public Alias(String name, String filter) {
this.name = name;
this.filter = filter;
}
/**
* Returns the alias name
*/
public String name() {
return name;
}
/**
* Returns the filter associated with the alias
*/
public String filter() {
return filter;
}
/**
* Associates a filter to the alias
*/
public Alias filter(String filter) {
this.filter = filter;
return this;
}
/**
* Associates a filter to the alias
*/
public Alias filter(Map<String, Object> filter) {
if (filter == null || filter.isEmpty()) {
this.filter = null;
return this;
}
try {
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
builder.map(filter);
this.filter = builder.string();
return this;
} catch (IOException e) {
throw new ElasticsearchGenerationException("Failed to generate [" + filter + "]", e);
}
}
/**
* Associates a filter to the alias
*/
public Alias filter(FilterBuilder filterBuilder) {
if (filterBuilder == null) {
this.filter = null;
return this;
}
try {
XContentBuilder builder = XContentFactory.jsonBuilder();
filterBuilder.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.close();
this.filter = builder.string();
return this;
} catch (IOException e) {
throw new ElasticsearchGenerationException("Failed to build json for alias request", e);
}
}
/**
* Associates a routing value to the alias
*/
public Alias routing(String routing) {
this.indexRouting = routing;
this.searchRouting = routing;
return this;
}
/**
* Returns the index routing value associated with the alias
*/
public String indexRouting() {
return indexRouting;
}
/**
* Associates an index routing value to the alias
*/
public Alias indexRouting(String indexRouting) {
this.indexRouting = indexRouting;
return this;
}
/**
* Returns the search routing value associated with the alias
*/
public String searchRouting() {
return searchRouting;
}
/**
* Associates a search routing value to the alias
*/
public Alias searchRouting(String searchRouting) {
this.searchRouting = searchRouting;
return this;
}
/**
* Allows to read an alias from the provided input stream
*/
public static Alias read(StreamInput in) throws IOException {
Alias alias = new Alias();
alias.readFrom(in);
return alias;
}
@Override
public void readFrom(StreamInput in) throws IOException {
name = in.readString();
filter = in.readOptionalString();
indexRouting = in.readOptionalString();
searchRouting = in.readOptionalString();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(name);
out.writeOptionalString(filter);
out.writeOptionalString(indexRouting);
out.writeOptionalString(searchRouting);
}
/**
* Parses an alias and returns its parsed representation
*/
public static Alias fromXContent(XContentParser parser) throws IOException {
Alias alias = new Alias(parser.currentName());
String currentFieldName = null;
XContentParser.Token token = parser.nextToken();
if (token == null) {
throw new ElasticsearchIllegalArgumentException("No alias is specified");
}
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName();
} else if (token == XContentParser.Token.START_OBJECT) {
if ("filter".equals(currentFieldName)) {
Map<String, Object> filter = parser.mapOrdered();
alias.filter(filter);
}
} else if (token == XContentParser.Token.VALUE_STRING) {
if ("routing".equals(currentFieldName)) {
alias.routing(parser.text());
} else if ("index_routing".equals(currentFieldName) || "indexRouting".equals(currentFieldName) || "index-routing".equals(currentFieldName)) {
alias.indexRouting(parser.text());
} else if ("search_routing".equals(currentFieldName) || "searchRouting".equals(currentFieldName) || "search-routing".equals(currentFieldName)) {
alias.searchRouting(parser.text());
}
}
}
return alias;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Alias alias = (Alias) o;
if (name != null ? !name.equals(alias.name) : alias.name != null) return false;
return true;
}
@Override
public int hashCode() {
return name != null ? name.hashCode() : 0;
}
}

View File

@ -21,6 +21,7 @@ package org.elasticsearch.action.admin.indices.create;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.cluster.ack.ClusterStateUpdateRequest;
import org.elasticsearch.cluster.block.ClusterBlock;
import org.elasticsearch.cluster.metadata.IndexMetaData;
@ -44,11 +45,13 @@ public class CreateIndexClusterStateUpdateRequest extends ClusterStateUpdateRequ
private Settings settings = ImmutableSettings.Builder.EMPTY_SETTINGS;
private Map<String, String> mappings = Maps.newHashMap();
private final Map<String, String> mappings = Maps.newHashMap();
private Map<String, IndexMetaData.Custom> customs = newHashMap();
private final Set<Alias> aliases = Sets.newHashSet();
private Set<ClusterBlock> blocks = Sets.newHashSet();
private final Map<String, IndexMetaData.Custom> customs = newHashMap();
private final Set<ClusterBlock> blocks = Sets.newHashSet();
CreateIndexClusterStateUpdateRequest(String cause, String index) {
@ -66,6 +69,11 @@ public class CreateIndexClusterStateUpdateRequest extends ClusterStateUpdateRequ
return this;
}
public CreateIndexClusterStateUpdateRequest aliases(Set<Alias> aliases) {
this.aliases.addAll(aliases);
return this;
}
public CreateIndexClusterStateUpdateRequest customs(Map<String, IndexMetaData.Custom> customs) {
this.customs.putAll(customs);
return this;
@ -101,6 +109,10 @@ public class CreateIndexClusterStateUpdateRequest extends ClusterStateUpdateRequ
return mappings;
}
public Set<Alias> aliases() {
return aliases;
}
public Map<String, IndexMetaData.Custom> customs() {
return customs;
}

View File

@ -20,10 +20,13 @@
package org.elasticsearch.action.admin.indices.create;
import com.google.common.base.Charsets;
import com.google.common.collect.Sets;
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.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.support.master.AcknowledgedRequest;
import org.elasticsearch.cluster.metadata.IndexMetaData;
@ -34,12 +37,11 @@ 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 java.io.IOException;
import java.util.Map;
import java.util.Set;
import static com.google.common.collect.Maps.newHashMap;
import static org.elasticsearch.action.ValidateActions.addValidationError;
@ -64,9 +66,11 @@ public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest>
private Settings settings = EMPTY_SETTINGS;
private Map<String, String> mappings = newHashMap();
private final Map<String, String> mappings = newHashMap();
private Map<String, IndexMetaData.Custom> customs = newHashMap();
private final Set<Alias> aliases = Sets.newHashSet();
private final Map<String, IndexMetaData.Custom> customs = newHashMap();
CreateIndexRequest() {
}
@ -244,6 +248,56 @@ public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest>
return this;
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
@SuppressWarnings("unchecked")
public CreateIndexRequest aliases(Map source) {
try {
XContentBuilder builder = XContentFactory.jsonBuilder();
builder.map(source);
return aliases(builder.string());
} 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 CreateIndexRequest aliases(XContentBuilder source) {
try {
return aliases(source.string());
} catch (IOException e) {
throw new ElasticsearchIllegalArgumentException("Failed to build json for aliases", e);
}
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
public CreateIndexRequest aliases(String source) {
try {
XContentParser parser = XContentHelper.createParser(new BytesArray(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 associated with the index when it gets created
*/
public CreateIndexRequest alias(Alias alias) {
this.aliases.add(alias);
return this;
}
/**
* Sets the settings and mappings as a single source.
*/
@ -306,6 +360,9 @@ public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest>
for (Map.Entry<String, Object> entry1 : mappings.entrySet()) {
mapping(entry1.getKey(), (Map<String, Object>) entry1.getValue());
}
} else if (name.equals("aliases")) {
found = true;
aliases((Map<String, Object>) entry.getValue());
} else {
// maybe custom?
IndexMetaData.Custom.Factory factory = IndexMetaData.lookupFactory(name);
@ -330,6 +387,10 @@ public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest>
return this.mappings;
}
Set<Alias> aliases() {
return this.aliases;
}
/**
* Adds custom metadata to the index to be created.
*/
@ -359,6 +420,12 @@ public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest>
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
@ -378,5 +445,11 @@ public class CreateIndexRequest extends AcknowledgedRequest<CreateIndexRequest>
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

@ -20,6 +20,7 @@
package org.elasticsearch.action.admin.indices.create;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.support.master.AcknowledgedRequestBuilder;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.client.internal.InternalIndicesAdminClient;
@ -149,6 +150,38 @@ public class CreateIndexRequestBuilder extends AcknowledgedRequestBuilder<Create
return this;
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
public CreateIndexRequestBuilder setAliases(Map source) {
request.aliases(source);
return this;
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
public CreateIndexRequestBuilder setAliases(String source) {
request.aliases(source);
return this;
}
/**
* Sets the aliases that will be associated with the index when it gets created
*/
public CreateIndexRequestBuilder setAliases(XContentBuilder source) {
request.aliases(source);
return this;
}
/**
* Adds an alias that will be associated with the index when it gets created
*/
public CreateIndexRequestBuilder addAlias(Alias alias) {
request.alias(alias);
return this;
}
/**
* Sets the settings and mappings as a single source.
*/

View File

@ -85,7 +85,7 @@ public class TransportCreateIndexAction extends TransportMasterNodeOperationActi
CreateIndexClusterStateUpdateRequest updateRequest = new CreateIndexClusterStateUpdateRequest(cause, request.index())
.ackTimeout(request.timeout()).masterNodeTimeout(request.masterNodeTimeout())
.settings(request.settings()).mappings(request.mappings())
.customs(request.customs());
.aliases(request.aliases()).customs(request.customs());
createIndexService.createIndex(updateRequest, new ClusterStateUpdateListener() {

View File

@ -0,0 +1,94 @@
/*
* 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.ElasticsearchIllegalArgumentException;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.indices.InvalidAliasNameException;
/**
* Validator for an alias, to be used before adding an alias to the index metadata
* and make sure the alias is valid
*/
public class AliasValidator extends AbstractComponent {
@Inject
public AliasValidator(Settings settings) {
super(settings);
}
/**
* Allows to validate an {@link org.elasticsearch.cluster.metadata.AliasAction} and make sure
* it's valid before it gets added to the index metadata
*/
public void validateAliasAction(AliasAction aliasAction, MetaData metaData) {
validateAlias(aliasAction.alias(), aliasAction.index(), aliasAction.indexRouting(), metaData);
}
/**
* 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
*/
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");
}
if (metaData.hasIndex(alias)) {
throw new InvalidAliasNameException(new Index(index), alias, "an index exists with the same name as the alias");
}
if (indexRouting != null && indexRouting.indexOf(',') != -1) {
throw new ElasticsearchIllegalArgumentException("alias [" + alias + "] has several index routing values associated with it");
}
}
/**
* 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, String filter, IndexQueryParserService indexQueryParserService) {
assert indexQueryParserService != null;
try {
XContentParser parser = XContentFactory.xContent(filter).createParser(filter);
try {
indexQueryParserService.parseInnerFilter(parser);
} finally {
parser.close();
}
} catch (Throwable e) {
throw new ElasticsearchIllegalArgumentException("failed to parse filter for alias [" + alias + "]", e);
}
}
}

View File

@ -28,6 +28,7 @@ import org.apache.lucene.util.CollectionUtil;
import org.apache.lucene.util.IOUtils;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.admin.indices.create.CreateIndexClusterStateUpdateRequest;
import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
import org.elasticsearch.cluster.ClusterService;
@ -59,6 +60,7 @@ import org.elasticsearch.index.Index;
import org.elasticsearch.index.mapper.DocumentMapper;
import org.elasticsearch.index.mapper.MapperParsingException;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.index.service.IndexService;
import org.elasticsearch.indices.IndexAlreadyExistsException;
import org.elasticsearch.indices.IndicesService;
@ -92,10 +94,12 @@ public class MetaDataCreateIndexService extends AbstractComponent {
private final MetaDataService metaDataService;
private final Version version;
private final String riverIndexName;
private final AliasValidator aliasValidator;
@Inject
public MetaDataCreateIndexService(Settings settings, Environment environment, ThreadPool threadPool, ClusterService clusterService, IndicesService indicesService,
AllocationService allocationService, MetaDataService metaDataService, Version version, @RiverIndexName String riverIndexName) {
AllocationService allocationService, MetaDataService metaDataService, Version version, @RiverIndexName String riverIndexName,
AliasValidator aliasValidator) {
super(settings);
this.environment = environment;
this.threadPool = threadPool;
@ -105,6 +109,7 @@ public class MetaDataCreateIndexService extends AbstractComponent {
this.metaDataService = metaDataService;
this.version = version;
this.riverIndexName = riverIndexName;
this.aliasValidator = aliasValidator;
}
public void createIndex(final CreateIndexClusterStateUpdateRequest request, final ClusterStateUpdateListener listener) {
@ -214,6 +219,10 @@ public class MetaDataCreateIndexService extends AbstractComponent {
try {
validate(request, currentState);
for (Alias alias : request.aliases()) {
aliasValidator.validateAlias(alias, request.index(), currentState.metaData());
}
// we only find a template when its an API call (a new index)
// find templates, highest order are better matching
List<IndexTemplateMetaData> templates = findTemplates(request, currentState);
@ -333,6 +342,14 @@ public class MetaDataCreateIndexService extends AbstractComponent {
throw new MapperParsingException("mapping [" + entry.getKey() + "]", e);
}
}
IndexQueryParserService indexQueryParserService = indexService.queryParserService();
for (Alias alias : request.aliases()) {
if (Strings.hasLength(alias.filter())) {
aliasValidator.validateAliasFilter(alias.name(), alias.filter(), indexQueryParserService);
}
}
// now, update the mappings with the actual source
Map<String, MappingMetaData> mappingsMetaData = Maps.newHashMap();
for (DocumentMapper mapper : mapperService) {
@ -344,6 +361,11 @@ public class MetaDataCreateIndexService extends AbstractComponent {
for (MappingMetaData mappingMd : mappingsMetaData.values()) {
indexMetaDataBuilder.putMapping(mappingMd);
}
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());
}

View File

@ -22,7 +22,6 @@ package org.elasticsearch.cluster.metadata;
import com.carrotsearch.hppc.cursors.ObjectCursor;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.action.admin.indices.alias.IndicesAliasesClusterStateUpdateRequest;
import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
import org.elasticsearch.cluster.ClusterService;
@ -37,15 +36,11 @@ import org.elasticsearch.common.component.AbstractComponent;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.query.IndexQueryParserService;
import org.elasticsearch.index.service.IndexService;
import org.elasticsearch.indices.IndexMissingException;
import org.elasticsearch.indices.IndicesService;
import org.elasticsearch.indices.InvalidAliasNameException;
import java.util.List;
import java.util.Map;
@ -59,11 +54,14 @@ public class MetaDataIndexAliasesService extends AbstractComponent {
private final IndicesService indicesService;
private final AliasValidator aliasValidator;
@Inject
public MetaDataIndexAliasesService(Settings settings, ClusterService clusterService, IndicesService indicesService) {
public MetaDataIndexAliasesService(Settings settings, ClusterService clusterService, IndicesService indicesService, AliasValidator aliasValidator) {
super(settings);
this.clusterService = clusterService;
this.indicesService = indicesService;
this.aliasValidator = aliasValidator;
}
public void indicesAliases(final IndicesAliasesClusterStateUpdateRequest request, final ClusterStateUpdateListener listener) {
@ -105,18 +103,10 @@ public class MetaDataIndexAliasesService extends AbstractComponent {
Map<String, IndexService> indices = Maps.newHashMap();
try {
for (AliasAction aliasAction : request.actions()) {
if (!Strings.hasText(aliasAction.alias()) || !Strings.hasText(aliasAction.index())) {
throw new ElasticsearchIllegalArgumentException("Index name and alias name are required");
}
aliasValidator.validateAliasAction(aliasAction, currentState.metaData());
if (!currentState.metaData().hasIndex(aliasAction.index())) {
throw new IndexMissingException(new Index(aliasAction.index()));
}
if (currentState.metaData().hasIndex(aliasAction.alias())) {
throw new InvalidAliasNameException(new Index(aliasAction.index()), aliasAction.alias(), "an index exists with the same name as the alias");
}
if (aliasAction.indexRouting() != null && aliasAction.indexRouting().indexOf(',') != -1) {
throw new ElasticsearchIllegalArgumentException("alias [" + aliasAction.alias() + "] has several routing values associated with it");
}
}
boolean changed = false;
@ -155,18 +145,7 @@ public class MetaDataIndexAliasesService extends AbstractComponent {
indices.put(indexMetaData.index(), indexService);
}
// now, parse the filter
IndexQueryParserService indexQueryParser = indexService.queryParserService();
try {
XContentParser parser = XContentFactory.xContent(filter).createParser(filter);
try {
indexQueryParser.parseInnerFilter(parser);
} finally {
parser.close();
}
} catch (Throwable e) {
throw new ElasticsearchIllegalArgumentException("failed to parse filter for [" + aliasAction.alias() + "]", e);
}
aliasValidator.validateAliasFilter(aliasAction.alias(), filter, indexService.queryParserService());
}
AliasMetaData newAliasMd = AliasMetaData.newAliasMetaDataBuilder(
aliasAction.alias())

View File

@ -19,7 +19,9 @@
package org.elasticsearch.aliases;
import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.admin.indices.alias.Alias;
import org.elasticsearch.action.admin.indices.alias.exists.AliasesExistResponse;
import org.elasticsearch.action.admin.indices.alias.get.GetAliasesResponse;
import org.elasticsearch.action.index.IndexResponse;
@ -33,6 +35,7 @@ import org.elasticsearch.common.settings.ImmutableSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.FilterBuilder;
import org.elasticsearch.index.query.FilterBuilders;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.indices.IndexMissingException;
import org.elasticsearch.rest.action.admin.indices.alias.delete.AliasesMissingException;
@ -819,6 +822,72 @@ public class IndexAliasesTests extends ElasticsearchIntegrationTest {
assertThat(response.getAliases(), hasKey("index1"));
}
@Test
public void testCreateIndexWithAliases() throws Exception {
assertAcked(prepareCreate("test").addAlias(new Alias("alias1"))
.addAlias(new Alias("alias2").filter(FilterBuilders.missingFilter("field")))
.addAlias(new Alias("alias3").indexRouting("index").searchRouting("search")));
checkAliases();
}
@Test
public void testCreateIndexWithAliasesInSource() throws Exception {
assertAcked(prepareCreate("test").setSource("{\n" +
" \"aliases\" : {\n" +
" \"alias1\" : {},\n" +
" \"alias2\" : {\"filter\" : {\"term\": {\"field\":\"value\"}}},\n" +
" \"alias3\" : { \"index_routing\" : \"index\", \"search_routing\" : \"search\"}\n" +
" }\n" +
"}"));
checkAliases();
}
@Test
public void testCreateIndexWithAliasesSource() throws Exception {
assertAcked(prepareCreate("test").setAliases("{\n" +
" \"alias1\" : {},\n" +
" \"alias2\" : {\"filter\" : {\"term\": {\"field\":\"value\"}}},\n" +
" \"alias3\" : { \"index_routing\" : \"index\", \"search_routing\" : \"search\"}\n" +
"}"));
checkAliases();
}
@Test (expected = ElasticsearchIllegalArgumentException.class)
public void testCreateIndexWithAliasesFilterNotValid() {
prepareCreate("test").addAlias(new Alias("alias1"))
.addAlias(new Alias("alias2").filter("f"))
.addAlias(new Alias("alias3").indexRouting("index").searchRouting("search")).get();
}
private void checkAliases() {
GetAliasesResponse getAliasesResponse = admin().indices().prepareGetAliases("alias1").get();
assertThat(getAliasesResponse.getAliases().get("test").size(), equalTo(1));
AliasMetaData aliasMetaData = getAliasesResponse.getAliases().get("test").get(0);
assertThat(aliasMetaData.alias(), equalTo("alias1"));
assertThat(aliasMetaData.filter(), nullValue());
assertThat(aliasMetaData.indexRouting(), nullValue());
assertThat(aliasMetaData.searchRouting(), nullValue());
getAliasesResponse = admin().indices().prepareGetAliases("alias2").get();
assertThat(getAliasesResponse.getAliases().get("test").size(), equalTo(1));
aliasMetaData = getAliasesResponse.getAliases().get("test").get(0);
assertThat(aliasMetaData.alias(), equalTo("alias2"));
assertThat(aliasMetaData.filter(), notNullValue());
assertThat(aliasMetaData.indexRouting(), nullValue());
assertThat(aliasMetaData.searchRouting(), nullValue());
getAliasesResponse = admin().indices().prepareGetAliases("alias3").get();
assertThat(getAliasesResponse.getAliases().get("test").size(), equalTo(1));
aliasMetaData = getAliasesResponse.getAliases().get("test").get(0);
assertThat(aliasMetaData.alias(), equalTo("alias3"));
assertThat(aliasMetaData.filter(), nullValue());
assertThat(aliasMetaData.indexRouting(), equalTo("index"));
assertThat(aliasMetaData.searchRouting(), equalTo("search"));
}
private void assertHits(SearchHits hits, String... ids) {
assertThat(hits.totalHits(), equalTo((long) ids.length));
Set<String> hitIds = newHashSet();