/*
* 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.routing.allocation;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ESAllocationTestCase;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.metadata.MetaData;
import org.elasticsearch.cluster.node.DiscoveryNodes;
import org.elasticsearch.cluster.routing.IndexRoutingTable;
import org.elasticsearch.cluster.routing.IndexShardRoutingTable;
import org.elasticsearch.cluster.routing.RoutingTable;
import org.elasticsearch.cluster.routing.ShardRouting;
import org.elasticsearch.cluster.routing.ShardRoutingState;
import org.elasticsearch.cluster.routing.TestShardRouting;
import org.elasticsearch.common.settings.Settings;
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.elasticsearch.cluster.routing.ShardRoutingState.INITIALIZING;
/**
* A base testcase that allows to run tests based on the output of the CAT API
* The input is a line based cat/shards output like:
* kibana-int 0 p STARTED 2 24.8kb 10.202.245.2 r5-9-35
*
* the test builds up a clusterstate from the cat input and optionally runs a full balance on it.
* This can be used to debug cluster allocation decisions.
*/
public abstract class CatAllocationTestCase extends ESAllocationTestCase {
protected abstract Path getCatPath() throws IOException;
public void testRun() throws IOException {
Set<String> nodes = new HashSet<>();
Map<String, Idx> indices = new HashMap<>();
try (BufferedReader reader = Files.newBufferedReader(getCatPath(), StandardCharsets.UTF_8)) {
String line = null;
// regexp FTW
Pattern pattern = Pattern.compile("^(.+)\\s+(\\d)\\s+([rp])\\s+(STARTED|RELOCATING|INITIALIZING|UNASSIGNED)" +
"\\s+\\d+\\s+[0-9.a-z]+\\s+(\\d+\\.\\d+\\.\\d+\\.\\d+).*$");
while((line = reader.readLine()) != null) {
final Matcher matcher;
if ((matcher = pattern.matcher(line)).matches()) {
final String index = matcher.group(1);
Idx idx = indices.get(index);
if (idx == null) {
idx = new Idx(index);
indices.put(index, idx);
}
final int shard = Integer.parseInt(matcher.group(2));
final boolean primary = matcher.group(3).equals("p");
ShardRoutingState state = ShardRoutingState.valueOf(matcher.group(4));
String ip = matcher.group(5);
nodes.add(ip);
ShardRouting routing = TestShardRouting.newShardRouting(index, shard, ip, null, primary, state);
idx.add(routing);
logger.debug("Add routing {}", routing);
} else {
fail("can't read line: " + line);
}
}
}
logger.info("Building initial routing table");
MetaData.Builder builder = MetaData.builder();
RoutingTable.Builder routingTableBuilder = RoutingTable.builder();
for(Idx idx : indices.values()) {
IndexMetaData.Builder idxMetaBuilder = IndexMetaData.builder(idx.name).settings(settings(Version.CURRENT))
.numberOfShards(idx.numShards()).numberOfReplicas(idx.numReplicas());
for (ShardRouting shardRouting : idx.routing) {
if (shardRouting.active()) {
Set<String> allocationIds = idxMetaBuilder.getInSyncAllocationIds(shardRouting.id());
if (allocationIds == null) {
allocationIds = new HashSet<>();
} else {
allocationIds = new HashSet<>(allocationIds);
}
allocationIds.add(shardRouting.allocationId().getId());
idxMetaBuilder.putInSyncAllocationIds(shardRouting.id(), allocationIds);
}
}
IndexMetaData idxMeta = idxMetaBuilder.build();
builder.put(idxMeta, false);
IndexRoutingTable.Builder tableBuilder = new IndexRoutingTable.Builder(idxMeta.getIndex()).initializeAsRecovery(idxMeta);
Map<Integer, IndexShardRoutingTable> shardIdToRouting = new HashMap<>();
for (ShardRouting r : idx.routing) {
IndexShardRoutingTable refData = new IndexShardRoutingTable.Builder(r.shardId()).addShard(r).build();
if (shardIdToRouting.containsKey(r.getId())) {
refData = new IndexShardRoutingTable.Builder(shardIdToRouting.get(r.getId())).addShard(r).build();
}
shardIdToRouting.put(r.getId(), refData);
}
for (IndexShardRoutingTable t: shardIdToRouting.values()) {
tableBuilder.addIndexShard(t);
}
IndexRoutingTable table = tableBuilder.build();
routingTableBuilder.add(table);
}
MetaData metaData = builder.build();
RoutingTable routingTable = routingTableBuilder.build();
DiscoveryNodes.Builder builderDiscoNodes = DiscoveryNodes.builder();
for (String node : nodes) {
builderDiscoNodes.add(newNode(node));
}
ClusterState clusterState = ClusterState.builder(org.elasticsearch.cluster.ClusterName.CLUSTER_NAME_SETTING
.getDefault(Settings.EMPTY)).metaData(metaData).routingTable(routingTable).nodes(builderDiscoNodes.build()).build();
if (balanceFirst()) {
clusterState = rebalance(clusterState);
}
clusterState = allocateNew(clusterState);
}
protected abstract ClusterState allocateNew(ClusterState clusterState);
protected boolean balanceFirst() {
return true;
}
private ClusterState rebalance(ClusterState clusterState) {
AllocationService strategy = createAllocationService(Settings.builder()
.build());
clusterState = strategy.reroute(clusterState, "reroute");
int numRelocations = 0;
while (true) {
List<ShardRouting> initializing = clusterState.routingTable().shardsWithState(INITIALIZING);
if (initializing.isEmpty()) {
break;
}
logger.debug("Initializing shards: {}", initializing);
numRelocations += initializing.size();
clusterState = strategy.applyStartedShards(clusterState, initializing);
}
logger.debug("--> num relocations to get balance: {}", numRelocations);
return clusterState;
}
public class Idx {
final String name;
final List<ShardRouting> routing = new ArrayList<>();
public Idx(String name) {
this.name = name;
}
public void add(ShardRouting r) {
routing.add(r);
}
public int numReplicas() {
int count = 0;
for (ShardRouting msr : routing) {
if (msr.primary() == false && msr.id()==0) {
count++;
}
}
return count;
}
public int numShards() {
int max = 0;
for (ShardRouting msr : routing) {
if (msr.primary()) {
max = Math.max(msr.getId()+1, max);
}
}
return max;
}
}
}