Add an ability to define and store filter for aliases.

This commit only adds an ability to store filter source. The stored filters are not yet used to filter search results.
This commit is contained in:
Igor Motov 2011-05-19 00:21:59 -04:00 committed by kimchy
parent d5759efed7
commit 573114a446
10 changed files with 378 additions and 8 deletions

View File

@ -19,15 +19,22 @@
package org.elasticsearch.action.admin.indices.alias;
import org.elasticsearch.ElasticSearchGenerationException;
import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.master.MasterNodeOperationRequest;
import org.elasticsearch.cluster.metadata.AliasAction;
import org.elasticsearch.common.collect.Lists;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.xcontent.XContentFilterBuilder;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import static org.elasticsearch.action.Actions.*;
import static org.elasticsearch.cluster.metadata.AliasAction.*;
@ -56,6 +63,62 @@ public class IndicesAliasesRequest extends MasterNodeOperationRequest {
return this;
}
/**
* Adds an alias to the index.
*
* @param index The index
* @param alias The alias
* @param filter The filter
*/
public IndicesAliasesRequest addAlias(String index, String alias, String filter) {
aliasActions.add(new AliasAction(AliasAction.Type.ADD, index, alias, filter));
return this;
}
/**
* Adds an alias to the index.
*
* @param index The index
* @param alias The alias
* @param filter The filter
*/
public IndicesAliasesRequest addAlias(String index, String alias, Map<String, Object> filter) {
if (filter == null || filter.isEmpty()) {
aliasActions.add(new AliasAction(AliasAction.Type.ADD, index, alias));
return this;
}
try {
XContentBuilder builder = XContentFactory.contentBuilder(XContentType.JSON);
builder.map(filter);
aliasActions.add(new AliasAction(AliasAction.Type.ADD, index, alias, builder.string()));
return this;
} catch (IOException e) {
throw new ElasticSearchGenerationException("Failed to generate [" + filter + "]", e);
}
}
/**
* Adds an alias to the index.
*
* @param index The index
* @param alias The alias
* @param filterBuilder The filter
*/
public IndicesAliasesRequest addAlias(String index, String alias, XContentFilterBuilder filterBuilder) {
if (filterBuilder == null) {
aliasActions.add(new AliasAction(AliasAction.Type.ADD, index, alias));
return this;
}
try {
XContentBuilder builder = XContentFactory.jsonBuilder();
filterBuilder.toXContent(builder, ToXContent.EMPTY_PARAMS);
builder.close();
return addAlias(index, alias, builder.string());
} catch (IOException e) {
throw new ElasticSearchGenerationException("Failed to build json for alias request", e);
}
}
/**
* Removes an alias to the index.
*

View File

@ -25,6 +25,9 @@ import org.elasticsearch.action.admin.indices.alias.IndicesAliasesResponse;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.client.action.admin.indices.support.BaseIndicesRequestBuilder;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.index.query.xcontent.XContentFilterBuilder;
import java.util.Map;
/**
* @author kimchy (shay.banon)
@ -46,6 +49,42 @@ public class IndicesAliasesRequestBuilder extends BaseIndicesRequestBuilder<Indi
return this;
}
/**
* Adds an alias to the index.
*
* @param index The index
* @param alias The alias
* @param filter The filter
*/
public IndicesAliasesRequestBuilder addAlias(String index, String alias, String filter) {
request.addAlias(index, alias, filter);
return this;
}
/**
* Adds an alias to the index.
*
* @param index The index
* @param alias The alias
* @param filter The filter
*/
public IndicesAliasesRequestBuilder addAlias(String index, String alias, Map<String, Object> filter) {
request.addAlias(index, alias, filter);
return this;
}
/**
* Adds an alias to the index.
*
* @param index The index
* @param alias The alias
* @param filterBuilder The filter
*/
public IndicesAliasesRequestBuilder addAlias(String index, String alias, XContentFilterBuilder filterBuilder) {
request.addAlias(index, alias, filterBuilder);
return this;
}
/**
* Removes an alias to the index.
*

View File

@ -62,14 +62,21 @@ public class AliasAction implements Streamable {
private String alias;
private String filter;
private AliasAction() {
}
public AliasAction(Type actionType, String index, String alias) {
this(actionType, index, alias, "");
}
public AliasAction(Type actionType, String index, String alias, String filter) {
this.actionType = actionType;
this.index = index;
this.alias = alias;
this.filter = filter;
}
public Type actionType() {
@ -84,6 +91,10 @@ public class AliasAction implements Streamable {
return alias;
}
public String filter() {
return filter;
}
public static AliasAction readAliasAction(StreamInput in) throws IOException {
AliasAction aliasAction = new AliasAction();
aliasAction.readFrom(in);
@ -94,11 +105,13 @@ public class AliasAction implements Streamable {
actionType = Type.fromValue(in.readByte());
index = in.readUTF();
alias = in.readUTF();
filter = in.readUTF();
}
@Override public void writeTo(StreamOutput out) throws IOException {
out.writeByte(actionType.value());
out.writeUTF(index);
out.writeUTF(alias);
out.writeUTF(filter);
}
}

View File

@ -19,12 +19,16 @@
package org.elasticsearch.cluster.metadata;
import org.elasticsearch.ElasticSearchGenerationException;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.compress.CompressedString;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.util.concurrent.Immutable;
import org.elasticsearch.common.xcontent.*;
import java.io.IOException;
import java.util.Map;
/**
* @author imotov
@ -34,8 +38,11 @@ public class AliasMetaData {
private final String alias;
private AliasMetaData(String alias) {
private final CompressedString filter;
private AliasMetaData(String alias, CompressedString filter) {
this.alias = alias;
this.filter = filter;
}
public String alias() {
@ -46,6 +53,14 @@ public class AliasMetaData {
return alias();
}
public CompressedString filter() {
return filter;
}
public CompressedString getFilter() {
return filter();
}
public static Builder newAliasMetaDataBuilder(String alias) {
return new Builder(alias);
}
@ -54,6 +69,8 @@ public class AliasMetaData {
private String alias;
private CompressedString filter;
public Builder(String alias) {
this.alias = alias;
}
@ -66,32 +83,102 @@ public class AliasMetaData {
return alias;
}
public Builder filter(String filter) {
if (!Strings.hasLength(filter)) {
this.filter = null;
return this;
}
try {
XContentParser parser = XContentFactory.xContent(filter).createParser(filter);
try {
filter(parser.mapOrdered());
} finally {
parser.close();
}
return this;
} catch (IOException e) {
throw new ElasticSearchGenerationException("Failed to generate [" + filter + "]", e);
}
}
public Builder filter(Map<String, Object> filter) {
if (filter == null || filter.isEmpty()) {
this.filter = null;
return this;
}
try {
this.filter = new CompressedString(XContentFactory.jsonBuilder().map(filter).string());
return this;
} catch (IOException e) {
throw new ElasticSearchGenerationException("Failed to build json for alias request", e);
}
}
public Builder filter(XContentBuilder filterBuilder) {
try {
return filter(filterBuilder.string());
} catch (IOException e) {
throw new ElasticSearchGenerationException("Failed to build json for alias request", e);
}
}
public AliasMetaData build() {
return new AliasMetaData(alias);
return new AliasMetaData(alias, filter);
}
public static void toXContent(AliasMetaData aliasMetaData, XContentBuilder builder, ToXContent.Params params) throws IOException {
builder.startObject(aliasMetaData.alias(), XContentBuilder.FieldCaseConversion.NONE);
// Filters will go here
if (aliasMetaData.filter() != null) {
byte[] data = aliasMetaData.filter().uncompressed();
XContentParser parser = XContentFactory.xContent(data).createParser(data);
Map<String, Object> filter = parser.mapOrdered();
parser.close();
builder.field("filter", filter);
}
builder.endObject();
}
public static AliasMetaData fromXContent(XContentParser parser) throws IOException {
Builder builder = new Builder(parser.currentName());
String currentFieldName = null;
XContentParser.Token token = parser.nextToken();
if (token == null) {
// no data...
return builder.build();
}
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
// Skip the content for now, filter and other settings will go here
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();
builder.filter(filter);
}
}
}
return builder.build();
}
public static void writeTo(AliasMetaData aliasMetaData, StreamOutput out) throws IOException {
out.writeUTF(aliasMetaData.alias());
if (aliasMetaData.filter() != null) {
out.writeBoolean(true);
aliasMetaData.filter.writeTo(out);
} else {
out.writeBoolean(false);
}
}
public static AliasMetaData readFrom(StreamInput in) throws IOException {
String alias = in.readUTF();
return new AliasMetaData(alias);
CompressedString filter = null;
if (in.readBoolean()) {
filter = CompressedString.readCompressedString(in);
}
return new AliasMetaData(alias, filter);
}
}

View File

@ -68,7 +68,7 @@ public class MetaDataIndexAliasesService extends AbstractComponent {
}
IndexMetaData.Builder indexMetaDataBuilder = newIndexMetaDataBuilder(indexMetaData);
if (aliasAction.actionType() == AliasAction.Type.ADD) {
indexMetaDataBuilder.putAlias(AliasMetaData.newAliasMetaDataBuilder(aliasAction.alias()).build());
indexMetaDataBuilder.putAlias(AliasMetaData.newAliasMetaDataBuilder(aliasAction.alias()).filter(aliasAction.filter()).build());
} else if (aliasAction.actionType() == AliasAction.Type.REMOVE) {
indexMetaDataBuilder.removerAlias(aliasAction.alias());
}

View File

@ -31,6 +31,7 @@ import org.elasticsearch.rest.action.admin.cluster.ping.broadcast.RestBroadcastP
import org.elasticsearch.rest.action.admin.cluster.ping.replication.RestReplicationPingAction;
import org.elasticsearch.rest.action.admin.cluster.ping.single.RestSinglePingAction;
import org.elasticsearch.rest.action.admin.cluster.state.RestClusterStateAction;
import org.elasticsearch.rest.action.admin.indices.alias.RestGetIndicesAliasesAction;
import org.elasticsearch.rest.action.admin.indices.alias.RestIndicesAliasesAction;
import org.elasticsearch.rest.action.admin.indices.analyze.RestAnalyzeAction;
import org.elasticsearch.rest.action.admin.indices.cache.clear.RestClearIndicesCacheAction;
@ -94,6 +95,7 @@ public class RestActionModule extends AbstractModule {
bind(RestReplicationPingAction.class).asEagerSingleton();
bind(RestIndicesStatusAction.class).asEagerSingleton();
bind(RestGetIndicesAliasesAction.class).asEagerSingleton();
bind(RestIndicesAliasesAction.class).asEagerSingleton();
bind(RestCreateIndexAction.class).asEagerSingleton();
bind(RestDeleteIndexAction.class).asEagerSingleton();

View File

@ -0,0 +1,99 @@
/*
* Licensed to Elastic Search and Shay Banon under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Elastic Search 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.rest.action.admin.indices.alias;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.cluster.state.ClusterStateRequest;
import org.elasticsearch.action.admin.cluster.state.ClusterStateResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.Requests;
import org.elasticsearch.cluster.metadata.AliasMetaData;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.*;
import org.elasticsearch.rest.action.support.RestXContentBuilder;
import java.io.IOException;
import static org.elasticsearch.rest.RestRequest.Method.GET;
import static org.elasticsearch.rest.RestStatus.OK;
import static org.elasticsearch.rest.action.support.RestActions.splitIndices;
/**
* @author imotov
*/
public class RestGetIndicesAliasesAction extends BaseRestHandler {
@Inject public RestGetIndicesAliasesAction(Settings settings, Client client, RestController controller) {
super(settings, client);
controller.registerHandler(GET, "/_aliases", this);
controller.registerHandler(GET, "/{index}/_aliases", this);
}
@Override public void handleRequest(final RestRequest request, final RestChannel channel) {
final String[] indices = splitIndices(request.param("index"));
ClusterStateRequest clusterStateRequest = Requests.clusterStateRequest()
.filterRoutingTable(true)
.filterNodes(true)
.filteredIndices(indices);
client.admin().cluster().state(clusterStateRequest, new ActionListener<ClusterStateResponse>() {
@Override public void onResponse(ClusterStateResponse response) {
try {
MetaData metaData = response.state().metaData();
XContentBuilder builder = RestXContentBuilder.restContentBuilder(request);
builder.startObject();
for (IndexMetaData indexMetaData : metaData) {
builder.startObject(indexMetaData.index());
builder.startObject("aliases");
for (AliasMetaData alias : indexMetaData.aliases().values()) {
AliasMetaData.Builder.toXContent(alias, builder, ToXContent.EMPTY_PARAMS);
}
builder.endObject();
builder.endObject();
}
builder.endObject();
channel.sendResponse(new XContentRestResponse(request, OK, builder));
} catch (Exception e) {
onFailure(e);
}
}
@Override public void onFailure(Throwable e) {
try {
channel.sendResponse(new XContentThrowableRestResponse(request, e));
} catch (IOException e1) {
logger.error("Failed to send failure response", e1);
}
}
});
}
}

View File

@ -33,6 +33,7 @@ import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.rest.*;
import java.io.IOException;
import java.util.Map;
import static org.elasticsearch.rest.RestRequest.Method.*;
import static org.elasticsearch.rest.RestStatus.*;
@ -53,7 +54,7 @@ public class RestIndicesAliasesAction extends BaseRestHandler {
try {
// {
// actions : [
// { add : { index : "test1", alias : "alias1" } }
// { add : { index : "test1", alias : "alias1", filter : {"user" : "kimchy"} } }
// { remove : { index : "test1", alias : "alias1" } }
// ]
// }
@ -78,6 +79,7 @@ public class RestIndicesAliasesAction extends BaseRestHandler {
}
String index = null;
String alias = null;
Map<String, Object> filter = null;
String currentFieldName = null;
while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
if (token == XContentParser.Token.FIELD_NAME) {
@ -88,6 +90,10 @@ public class RestIndicesAliasesAction extends BaseRestHandler {
} else if ("alias".equals(currentFieldName)) {
alias = parser.text();
}
} else if (token == XContentParser.Token.START_OBJECT) {
if ("filter".equals(currentFieldName)) {
filter = parser.mapOrdered();
}
}
}
if (index == null) {
@ -97,7 +103,7 @@ public class RestIndicesAliasesAction extends BaseRestHandler {
throw new ElasticSearchIllegalArgumentException("Alias action [" + action + "] requires an [alias] to be set");
}
if (type == AliasAction.Type.ADD) {
indicesAliasesRequest.addAlias(index, alias);
indicesAliasesRequest.addAlias(index, alias, filter);
} else if (type == AliasAction.Type.REMOVE) {
indicesAliasesRequest.removeAlias(index, alias);
}

View File

@ -78,6 +78,19 @@ public class ToAndFromJsonMetaDataTests {
.putMapping("mapping2", MAPPING_SOURCE2)
.putAlias(newAliasMetaDataBuilder("alias1"))
.putAlias(newAliasMetaDataBuilder("alias2")))
.put(newIndexMetaDataBuilder("test7")
.settings(settingsBuilder()
.put("setting1", "value1")
.put("setting2", "value2")
.put("index.aliases.0", "alias3")
.put("index.aliases.1", "alias1"))
.numberOfShards(1)
.numberOfReplicas(2)
.putMapping("mapping1", MAPPING_SOURCE1)
.putMapping("mapping2", MAPPING_SOURCE2)
.putAlias(newAliasMetaDataBuilder("alias1").filter(ALIAS_FILTER1))
.putAlias(newAliasMetaDataBuilder("alias2"))
.putAlias(newAliasMetaDataBuilder("alias4").filter(ALIAS_FILTER2)))
.build();
String metaDataSource = MetaData.Builder.toXContent(metaData);
@ -142,8 +155,29 @@ public class ToAndFromJsonMetaDataTests {
assertThat(indexMetaData.aliases().get("alias1").alias(), equalTo("alias1"));
assertThat(indexMetaData.aliases().get("alias2").alias(), equalTo("alias2"));
assertThat(indexMetaData.aliases().get("alias3").alias(), equalTo("alias3"));
indexMetaData = parsedMetaData.index("test7");
assertThat(indexMetaData.numberOfShards(), equalTo(1));
assertThat(indexMetaData.numberOfReplicas(), equalTo(2));
assertThat(indexMetaData.settings().getAsMap().size(), equalTo(4));
assertThat(indexMetaData.settings().get("setting1"), equalTo("value1"));
assertThat(indexMetaData.settings().get("setting2"), equalTo("value2"));
assertThat(indexMetaData.mappings().size(), equalTo(2));
assertThat(indexMetaData.mappings().get("mapping1").source().string(), equalTo(MAPPING_SOURCE1));
assertThat(indexMetaData.mappings().get("mapping2").source().string(), equalTo(MAPPING_SOURCE2));
assertThat(indexMetaData.aliases().size(), equalTo(4));
assertThat(indexMetaData.aliases().get("alias1").alias(), equalTo("alias1"));
assertThat(indexMetaData.aliases().get("alias1").filter().string(), equalTo(ALIAS_FILTER1));
assertThat(indexMetaData.aliases().get("alias2").alias(), equalTo("alias2"));
assertThat(indexMetaData.aliases().get("alias2").filter(), nullValue());
assertThat(indexMetaData.aliases().get("alias3").alias(), equalTo("alias3"));
assertThat(indexMetaData.aliases().get("alias3").filter(), nullValue());
assertThat(indexMetaData.aliases().get("alias4").alias(), equalTo("alias4"));
assertThat(indexMetaData.aliases().get("alias4").filter().string(), equalTo(ALIAS_FILTER2));
}
private static final String MAPPING_SOURCE1 = "{\"mapping1\":{\"text1\":{\"type\":\"string\"}}}";
private static final String MAPPING_SOURCE2 = "{\"mapping2\":{\"text2\":{\"type\":\"string\"}}}";
private static final String ALIAS_FILTER1 = "{\"field1\":\"value1\"}";
private static final String ALIAS_FILTER2 = "{\"field2\":\"value2\"}";
}

View File

@ -23,6 +23,9 @@ import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.index.query.xcontent.XContentFilterBuilder;
import org.elasticsearch.indices.IndexMissingException;
import org.elasticsearch.test.integration.AbstractNodesTests;
import org.testng.annotations.AfterMethod;
@ -30,6 +33,7 @@ import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import static org.elasticsearch.client.Requests.*;
import static org.elasticsearch.index.query.xcontent.FilterBuilders.termFilter;
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;
@ -108,6 +112,29 @@ public class IndexAliasesTests extends AbstractNodesTests {
assertThat(indexResponse.index(), equalTo("test_x"));
}
@Test public void testFilteringAliases() throws Exception {
logger.info("--> creating index [test]");
client1.admin().indices().create(createIndexRequest("test")).actionGet();
logger.info("--> running cluster_health");
ClusterHealthResponse clusterHealth = client1.admin().cluster().health(clusterHealthRequest().waitForGreenStatus()).actionGet();
logger.info("--> done cluster_health, status " + clusterHealth.status());
assertThat(clusterHealth.timedOut(), equalTo(false));
assertThat(clusterHealth.status(), equalTo(ClusterHealthStatus.GREEN));
logger.info("--> aliasing index [test] with [alias1] and filter [user:kimchy]");
XContentFilterBuilder filter = termFilter("user", "kimchy");
client1.admin().indices().prepareAliases().addAlias("test", "alias1", filter).execute().actionGet();
Thread.sleep(300);
// For now just making sure that filter was stored with the alias
logger.info("--> making sure that filter was stored with alias [alias1] and filter [user:kimchy]");
ClusterState clusterState = client1.admin().cluster().prepareState().execute().actionGet().state();
IndexMetaData indexMd = clusterState.metaData().index("test");
assertThat(indexMd.aliases().get("alias1").filter().string(), equalTo("{\"term\":{\"user\":\"kimchy\"}}"));
}
private String source(String id, String nameValue) {
return "{ type1 : { \"id\" : \"" + id + "\", \"name\" : \"" + nameValue + "\" } }";
}