/*
* 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.percolator;
import com.google.common.base.Predicate;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.indices.delete.DeleteIndexResponse;
import org.elasticsearch.action.count.CountResponse;
import org.elasticsearch.action.percolate.MultiPercolateRequestBuilder;
import org.elasticsearch.action.percolate.MultiPercolateResponse;
import org.elasticsearch.action.percolate.PercolateRequestBuilder;
import org.elasticsearch.action.percolate.PercolateResponse;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.Requests;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.common.Priority;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.test.ESIntegTestCase;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import static org.elasticsearch.action.percolate.PercolateSourceBuilder.docBuilder;
import static org.elasticsearch.client.Requests.clusterHealthRequest;
import static org.elasticsearch.common.settings.Settings.settingsBuilder;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.index.query.QueryBuilders.*;
import static org.elasticsearch.percolator.PercolatorTestUtil.convertFromTextArray;
import static org.elasticsearch.test.ESIntegTestCase.ClusterScope;
import static org.elasticsearch.test.ESIntegTestCase.Scope;
import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.*;
import static org.hamcrest.Matchers.*;
@ClusterScope(scope = Scope.TEST, numDataNodes = 0, numClientNodes = 0, transportClientRatio = 0)
public class RecoveryPercolatorIT extends ESIntegTestCase {
@Override
protected int numberOfShards() {
return 1;
}
@Test
public void testRestartNodePercolator1() throws Exception {
internalCluster().startNode();
assertAcked(prepareCreate("test").addMapping("type1", "field1", "type=string").addMapping(PercolatorService.TYPE_NAME, "color", "type=string"));
logger.info("--> register a query");
client().prepareIndex("test", PercolatorService.TYPE_NAME, "kuku")
.setSource(jsonBuilder().startObject()
.field("color", "blue")
.field("query", termQuery("field1", "value1"))
.endObject())
.setRefresh(true)
.get();
PercolateResponse percolate = client().preparePercolate()
.setIndices("test").setDocumentType("type1")
.setSource(jsonBuilder().startObject().startObject("doc")
.field("field1", "value1")
.endObject().endObject())
.get();
assertThat(percolate.getMatches(), arrayWithSize(1));
internalCluster().rollingRestart();
logger.info("Running Cluster Health (wait for the shards to startup)");
ensureYellow();
percolate = client().preparePercolate()
.setIndices("test").setDocumentType("type1")
.setSource(jsonBuilder().startObject().startObject("doc")
.field("field1", "value1")
.endObject().endObject())
.get();
assertMatchCount(percolate, 1l);
assertThat(percolate.getMatches(), arrayWithSize(1));
}
@Test
public void testRestartNodePercolator2() throws Exception {
internalCluster().startNode();
assertAcked(prepareCreate("test").addMapping("type1", "field1", "type=string").addMapping(PercolatorService.TYPE_NAME, "color", "type=string"));
logger.info("--> register a query");
client().prepareIndex("test", PercolatorService.TYPE_NAME, "kuku")
.setSource(jsonBuilder().startObject()
.field("color", "blue")
.field("query", termQuery("field1", "value1"))
.endObject())
.setRefresh(true)
.get();
assertThat(client().prepareCount().setTypes(PercolatorService.TYPE_NAME).setQuery(matchAllQuery()).get().getCount(), equalTo(1l));
PercolateResponse percolate = client().preparePercolate()
.setIndices("test").setDocumentType("type1")
.setSource(jsonBuilder().startObject().startObject("doc")
.field("field1", "value1")
.endObject().endObject())
.get();
assertMatchCount(percolate, 1l);
assertThat(percolate.getMatches(), arrayWithSize(1));
internalCluster().rollingRestart();
logger.info("Running Cluster Health (wait for the shards to startup)");
ClusterHealthResponse clusterHealth = client().admin().cluster().health(clusterHealthRequest().waitForYellowStatus().waitForActiveShards(1)).actionGet();
logger.info("Done Cluster Health, status " + clusterHealth.getStatus());
assertThat(clusterHealth.isTimedOut(), equalTo(false));
CountResponse countResponse = client().prepareCount().setTypes(PercolatorService.TYPE_NAME).setQuery(matchAllQuery()).get();
assertHitCount(countResponse, 1l);
DeleteIndexResponse actionGet = client().admin().indices().prepareDelete("test").get();
assertThat(actionGet.isAcknowledged(), equalTo(true));
client().admin().indices().prepareCreate("test").setSettings(settingsBuilder().put("index.number_of_shards", 1)).get();
clusterHealth = client().admin().cluster().health(clusterHealthRequest().waitForYellowStatus().waitForActiveShards(1)).actionGet();
logger.info("Done Cluster Health, status " + clusterHealth.getStatus());
assertThat(clusterHealth.isTimedOut(), equalTo(false));
assertThat(client().prepareCount().setTypes(PercolatorService.TYPE_NAME).setQuery(matchAllQuery()).get().getCount(), equalTo(0l));
percolate = client().preparePercolate()
.setIndices("test").setDocumentType("type1")
.setSource(jsonBuilder().startObject().startObject("doc")
.field("field1", "value1")
.endObject().endObject())
.get();
assertMatchCount(percolate, 0l);
assertThat(percolate.getMatches(), emptyArray());
logger.info("--> register a query");
client().prepareIndex("test", PercolatorService.TYPE_NAME, "kuku")
.setSource(jsonBuilder().startObject()
.field("color", "blue")
.field("query", termQuery("field1", "value1"))
.endObject())
.setRefresh(true)
.get();
assertThat(client().prepareCount().setTypes(PercolatorService.TYPE_NAME).setQuery(matchAllQuery()).get().getCount(), equalTo(1l));
percolate = client().preparePercolate()
.setIndices("test").setDocumentType("type1")
.setSource(jsonBuilder().startObject().startObject("doc")
.field("field1", "value1")
.endObject().endObject())
.get();
assertMatchCount(percolate, 1l);
assertThat(percolate.getMatches(), arrayWithSize(1));
}
@Test
public void testLoadingPercolateQueriesDuringCloseAndOpen() throws Exception {
internalCluster().startNode();
internalCluster().startNode();
assertAcked(client().admin().indices().prepareCreate("test")
.setSettings(settingsBuilder().put(IndexMetaData.SETTING_NUMBER_OF_SHARDS, 2)
.put(IndexMetaData.SETTING_NUMBER_OF_REPLICAS, 1)));
ensureGreen();
logger.info("--> Add dummy docs");
client().prepareIndex("test", "type1", "1").setSource("field1", 0).get();
client().prepareIndex("test", "type2", "1").setSource("field1", 1).get();
logger.info("--> register a queries");
for (int i = 1; i <= 100; i++) {
client().prepareIndex("test", PercolatorService.TYPE_NAME, Integer.toString(i))
.setSource(jsonBuilder().startObject()
.field("query", rangeQuery("field1").from(0).to(i))
.endObject())
.get();
}
logger.info("--> Percolate doc with field1=95");
PercolateResponse response = client().preparePercolate()
.setIndices("test").setDocumentType("type1")
.setSource(jsonBuilder().startObject().startObject("doc").field("field1", 95).endObject().endObject())
.get();
assertMatchCount(response, 6l);
assertThat(response.getMatches(), arrayWithSize(6));
assertThat(convertFromTextArray(response.getMatches(), "test"), arrayContainingInAnyOrder("95", "96", "97", "98", "99", "100"));
logger.info("--> Close and open index to trigger percolate queries loading...");
assertAcked(client().admin().indices().prepareClose("test"));
assertAcked(client().admin().indices().prepareOpen("test"));
ensureGreen();
logger.info("--> Percolate doc with field1=100");
response = client().preparePercolate()
.setIndices("test").setDocumentType("type1")
.setSource(jsonBuilder().startObject().startObject("doc").field("field1", 100).endObject().endObject()).get();
assertMatchCount(response, 1l);
assertThat(response.getMatches(), arrayWithSize(1));
assertThat(response.getMatches()[0].getId().string(), equalTo("100"));
}
@Test
public void testSinglePercolator_recovery() throws Exception {
percolatorRecovery(false);
}
@Test
public void testMultiPercolator_recovery() throws Exception {
percolatorRecovery(true);
}
// 3 nodes, 2 primary + 2 replicas per primary, so each node should have a copy of the data.
// We only start and stop nodes 2 and 3, so all requests should succeed and never be partial.
private void percolatorRecovery(final boolean multiPercolate) throws Exception {
internalCluster().startNode(settingsBuilder().put("node.stay", true));
internalCluster().startNode(settingsBuilder().put("node.stay", false));
internalCluster().startNode(settingsBuilder().put("node.stay", false));
ensureGreen();
client().admin().indices().prepareCreate("test")
.setSettings(settingsBuilder()
.put("index.number_of_shards", 2)
.put("index.number_of_replicas", 2)
)
.get();
ensureGreen();
final Client client = internalCluster().client(new Predicate<Settings>() {
@Override
public boolean apply(Settings input) {
return input.getAsBoolean("node.stay", true);
}
});
final int numQueries = randomIntBetween(50, 100);
logger.info("--> register a queries");
for (int i = 0; i < numQueries; i++) {
client.prepareIndex("test", PercolatorService.TYPE_NAME, Integer.toString(i))
.setSource(jsonBuilder().startObject().field("query", matchAllQuery()).endObject())
.get();
}
final String document = "{\"field\" : \"a\"}";
client.prepareIndex("test", "type", "1")
.setSource(document)
.get();
final AtomicBoolean run = new AtomicBoolean(true);
final AtomicReference<Throwable> error = new AtomicReference<>();
Runnable r = new Runnable() {
@Override
public void run() {
try {
while (run.get()) {
if (multiPercolate) {
MultiPercolateRequestBuilder builder = client
.prepareMultiPercolate();
int numPercolateRequest = randomIntBetween(50, 100);
for (int i = 0; i < numPercolateRequest; i++) {
PercolateRequestBuilder percolateBuilder = client.preparePercolate()
.setIndices("test").setDocumentType("type");
if (randomBoolean()) {
percolateBuilder.setGetRequest(Requests.getRequest("test").type("type").id("1"));
} else {
percolateBuilder.setPercolateDoc(docBuilder().setDoc(document));
}
builder.add(percolateBuilder);
}
MultiPercolateResponse response = builder.get();
assertThat(response.items().length, equalTo(numPercolateRequest));
for (MultiPercolateResponse.Item item : response) {
assertThat(item.isFailure(), equalTo(false));
assertNoFailures(item.getResponse());
assertThat(item.getResponse().getSuccessfulShards(), equalTo(item.getResponse().getTotalShards()));
assertThat(item.getResponse().getCount(), equalTo((long) numQueries));
assertThat(item.getResponse().getMatches().length, equalTo(numQueries));
}
} else {
PercolateRequestBuilder percolateBuilder = client.preparePercolate()
.setIndices("test").setDocumentType("type");
if (randomBoolean()) {
percolateBuilder.setPercolateDoc(docBuilder().setDoc(document));
} else {
percolateBuilder.setGetRequest(Requests.getRequest("test").type("type").id("1"));
}
PercolateResponse response = percolateBuilder.get();
assertNoFailures(response);
assertThat(response.getSuccessfulShards(), equalTo(response.getTotalShards()));
assertThat(response.getCount(), equalTo((long) numQueries));
assertThat(response.getMatches().length, equalTo(numQueries));
}
}
} catch (Throwable t) {
logger.info("Error in percolate thread...", t);
run.set(false);
error.set(t);
}
}
};
Thread t = new Thread(r);
t.start();
Predicate<Settings> nodePredicate = new Predicate<Settings>() {
@Override
public boolean apply(Settings input) {
return !input.getAsBoolean("node.stay", false);
}
};
try {
// 1 index, 2 primaries, 2 replicas per primary
for (int i = 0; i < 4; i++) {
internalCluster().stopRandomNode(nodePredicate);
client.admin().cluster().prepareHealth("test")
.setWaitForEvents(Priority.LANGUID)
.setTimeout(TimeValue.timeValueMinutes(2))
.setWaitForYellowStatus()
.setWaitForActiveShards(4) // 2 nodes, so 4 shards (2 primaries, 2 replicas)
.get();
assertThat(error.get(), nullValue());
internalCluster().stopRandomNode(nodePredicate);
client.admin().cluster().prepareHealth("test")
.setWaitForEvents(Priority.LANGUID)
.setTimeout(TimeValue.timeValueMinutes(2))
.setWaitForYellowStatus()
.setWaitForActiveShards(2) // 1 node, so 2 shards (2 primaries, 0 replicas)
.get();
assertThat(error.get(), nullValue());
internalCluster().startNode();
client.admin().cluster().prepareHealth("test")
.setWaitForEvents(Priority.LANGUID)
.setTimeout(TimeValue.timeValueMinutes(2))
.setWaitForYellowStatus()
.setWaitForActiveShards(4) // 2 nodes, so 4 shards (2 primaries, 2 replicas)
.get();
assertThat(error.get(), nullValue());
internalCluster().startNode();
client.admin().cluster().prepareHealth("test")
.setWaitForEvents(Priority.LANGUID)
.setTimeout(TimeValue.timeValueMinutes(2))
.setWaitForGreenStatus() // We're confirm the shard settings, so green instead of yellow
.setWaitForActiveShards(6) // 3 nodes, so 6 shards (2 primaries, 4 replicas)
.get();
assertThat(error.get(), nullValue());
}
} finally {
run.set(false);
}
t.join();
assertThat(error.get(), nullValue());
}
}