package io.searchbox.cluster;
import com.google.common.collect.ImmutableList;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import io.searchbox.client.JestResult;
import io.searchbox.cluster.reroute.RerouteAllocateReplica;
import io.searchbox.cluster.reroute.RerouteCancel;
import io.searchbox.cluster.reroute.RerouteCommand;
import io.searchbox.cluster.reroute.RerouteMove;
import io.searchbox.common.AbstractIntegrationTest;
import io.searchbox.core.Cat;
import io.searchbox.core.CatResult;
import org.elasticsearch.test.ESIntegTestCase;
import org.junit.After;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import java.io.IOException;
import java.util.HashSet;
import java.util.List;
import java.util.Set ;
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 3)
public class RerouteIntegrationTest extends AbstractIntegrationTest {
static final String INDEX = "reroute";
@Before
public void beforeTest() throws IOException {
createIndex(INDEX);
ensureSearchable(INDEX);
setAllocationDisabled(true);
}
@After
public void afterTest() throws IOException {
setAllocationDisabled(false);
}
@Test
public void move() throws IOException, InterruptedException {
int shardToReroute = 0;
String fromNode = getNodeOfPrimaryShard(INDEX, shardToReroute);
String toNode = getAvailableNodeForShard(INDEX, shardToReroute);
RerouteMove rerouteMove = new RerouteMove(INDEX, shardToReroute, fromNode, toNode);
JestResult result = client.execute(new Reroute.Builder(rerouteMove).build());
assertTrue(result.getErrorMessage(), result.isSucceeded());
waitUntilPrimaryShardInNode(shardToReroute, toNode);
}
@Test
@Ignore("[allocate_replica] all copies of [reroute][0] are already assigned. Use the move allocation command instead")
public void cancelAndAllocate() throws IOException, InterruptedException {
int shardToReroute = 0;
String fromNode = getNodeOfPrimaryShard(INDEX, shardToReroute);
String toNode = getAvailableNodeForShard(INDEX, shardToReroute);
List<RerouteCommand> commands = ImmutableList.of(
new RerouteCancel(INDEX, shardToReroute, fromNode, true),
new RerouteAllocateReplica(INDEX, shardToReroute, toNode)
);
JestResult result = client.execute(new Reroute.Builder(commands).build());
assertTrue(result.getErrorMessage(), result.isSucceeded());
waitUntilPrimaryShardInNode(shardToReroute, toNode);
}
private void setAllocationDisabled(boolean disabled) throws IOException {
String state = disabled ? "none" : "all";
String source = "{\n" +
" \"transient\" : {\n" +
" \"cluster.routing.allocation.enable\": \"" + state + "\" " +
" }\n" +
"}";
UpdateSettings updateSettings = new UpdateSettings.Builder(source).build();
JestResult result = client.execute(updateSettings);
assertTrue(result.getErrorMessage(), result.isSucceeded());
}
private String getAvailableNodeForShard(String index, int shard) throws IOException {
Set<String> dataNodes = getAllDataNodes();
String primaryShardNode = getNodeOfPrimaryShard(index, shard);
Set<String> replicaShardNodes = getNodesOfReplicaShard(index, shard);
dataNodes.remove(primaryShardNode);
dataNodes.removeAll(replicaShardNodes);
if (dataNodes.size() < 1) {
throw new RuntimeException("No Available node for shard=" + shard + " index=" + index);
}
return dataNodes.iterator().next();
}
private Set<String> getAllDataNodes() throws IOException {
CatResult result = client.execute(new Cat.NodesBuilder().build());
JsonArray nodes = result.getJsonObject().get("result").getAsJsonArray();
Set<String> nodeNames = new HashSet<String>();
for (JsonElement nodeElement : nodes) {
JsonObject nodeObj = nodeElement.getAsJsonObject();
String nodeRole = nodeObj.get("node.role").getAsString();
if (nodeRole.indexOf('d') >= 0) {
nodeNames.add(nodeObj.get("name").getAsString());
}
}
return nodeNames;
}
private String getNodeOfPrimaryShard(String index, int shard) throws IOException {
Set<String> nodeOfShard = getNodeOfShard(index, shard, true);
if (nodeOfShard.size() == 0) {
return null;
}
return nodeOfShard.iterator().next();
}
private Set<String> getNodesOfReplicaShard(String index, int shard) throws IOException {
return getNodeOfShard(index, shard, false);
}
private Set<String> getNodeOfShard(String index, int shard, boolean isPrimary) throws IOException {
char prirep = isPrimary ? 'p' : 'r';
CatResult result = client.execute(new Cat.ShardsBuilder().addIndex(index).setParameter("h", "shard,node,prirep").build());
JsonArray shards = result.getJsonObject().get("result").getAsJsonArray();
Set<String> resultSet = new HashSet<>();
for (JsonElement shardElement : shards) {
JsonObject shardObj = shardElement.getAsJsonObject();
if (shardObj.get("shard").getAsInt() == shard && shardObj.get("prirep").getAsCharacter() == prirep) {
resultSet.add(shardObj.get("node").getAsString());
}
}
return resultSet;
}
private void waitUntilPrimaryShardInNode(int shard, String expectedNode) throws InterruptedException, IOException {
int retries = 0;
int maxAttempts = 3;
String currentNode = getNodeOfPrimaryShard(INDEX, shard);
while (retries < 3 && currentNode != null && !currentNode.equals(expectedNode)) {
retries++;
currentNode = getNodeOfPrimaryShard(INDEX, shard);
Thread.sleep(1000);
}
if (retries >= maxAttempts) {
fail("Primary shard " + shard + " expected to be in node " + expectedNode + " but is in " + currentNode);
}
}
}