/*
* 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;
import org.apache.lucene.util.IOUtils;
import org.elasticsearch.Version;
import org.elasticsearch.action.support.replication.ClusterStateCreationUtils;
import org.elasticsearch.cluster.metadata.IndexMetaData;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.shard.ShardId;
import org.elasticsearch.test.ClusterServiceUtils;
import org.elasticsearch.test.ESTestCase;
import org.elasticsearch.threadpool.TestThreadPool;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.object.HasToString.hasToString;
public class OperationRoutingTests extends ESTestCase{
public void testGenerateShardId() {
int[][] possibleValues = new int[][] {
{8,4,2}, {20, 10, 2}, {36, 12, 3}, {15,5,1}
};
for (int i = 0; i < 10; i++) {
int[] shardSplits = randomFrom(possibleValues);
assertEquals(shardSplits[0], (shardSplits[0] / shardSplits[1]) * shardSplits[1]);
assertEquals(shardSplits[1], (shardSplits[1] / shardSplits[2]) * shardSplits[2]);
IndexMetaData metaData = IndexMetaData.builder("test").settings(settings(Version.CURRENT)).numberOfShards(shardSplits[0])
.numberOfReplicas(1).build();
String term = randomAlphaOfLength(10);
final int shard = OperationRouting.generateShardId(metaData, term, null);
IndexMetaData shrunk = IndexMetaData.builder("test").settings(settings(Version.CURRENT)).numberOfShards(shardSplits[1])
.numberOfReplicas(1)
.setRoutingNumShards(shardSplits[0]).build();
int shrunkShard = OperationRouting.generateShardId(shrunk, term, null);
Set<ShardId> shardIds = IndexMetaData.selectShrinkShards(shrunkShard, metaData, shrunk.getNumberOfShards());
assertEquals(1, shardIds.stream().filter((sid) -> sid.id() == shard).count());
shrunk = IndexMetaData.builder("test").settings(settings(Version.CURRENT)).numberOfShards(shardSplits[2]).numberOfReplicas(1)
.setRoutingNumShards(shardSplits[0]).build();
shrunkShard = OperationRouting.generateShardId(shrunk, term, null);
shardIds = IndexMetaData.selectShrinkShards(shrunkShard, metaData, shrunk.getNumberOfShards());
assertEquals(Arrays.toString(shardSplits), 1, shardIds.stream().filter((sid) -> sid.id() == shard).count());
}
}
public void testPartitionedIndex() {
// make sure the same routing value always has each _id fall within the configured partition size
for (int shards = 1; shards < 5; shards++) {
for (int partitionSize = 1; partitionSize == 1 || partitionSize < shards; partitionSize++) {
IndexMetaData metaData = IndexMetaData.builder("test")
.settings(settings(Version.CURRENT))
.numberOfShards(shards)
.routingPartitionSize(partitionSize)
.numberOfReplicas(1)
.build();
for (int i = 0; i < 20; i++) {
String routing = randomUnicodeOfLengthBetween(1, 50);
Set<Integer> shardSet = new HashSet<>();
for (int k = 0; k < 150; k++) {
String id = randomUnicodeOfLengthBetween(1, 50);
shardSet.add(OperationRouting.generateShardId(metaData, id, routing));
}
assertEquals(partitionSize, shardSet.size());
}
}
}
}
public void testPartitionedIndexShrunk() {
Map<String, Map<String, Integer>> routingIdToShard = new HashMap<>();
Map<String, Integer> routingA = new HashMap<>();
routingA.put("a_0", 1);
routingA.put("a_1", 2);
routingA.put("a_2", 2);
routingA.put("a_3", 2);
routingA.put("a_4", 1);
routingA.put("a_5", 2);
routingIdToShard.put("a", routingA);
Map<String, Integer> routingB = new HashMap<>();
routingB.put("b_0", 0);
routingB.put("b_1", 0);
routingB.put("b_2", 0);
routingB.put("b_3", 0);
routingB.put("b_4", 3);
routingB.put("b_5", 3);
routingIdToShard.put("b", routingB);
Map<String, Integer> routingC = new HashMap<>();
routingC.put("c_0", 1);
routingC.put("c_1", 1);
routingC.put("c_2", 0);
routingC.put("c_3", 0);
routingC.put("c_4", 0);
routingC.put("c_5", 1);
routingIdToShard.put("c", routingC);
Map<String, Integer> routingD = new HashMap<>();
routingD.put("d_0", 2);
routingD.put("d_1", 2);
routingD.put("d_2", 3);
routingD.put("d_3", 3);
routingD.put("d_4", 3);
routingD.put("d_5", 3);
routingIdToShard.put("d", routingD);
IndexMetaData metaData = IndexMetaData.builder("test")
.settings(settings(Version.CURRENT))
.setRoutingNumShards(8)
.numberOfShards(4)
.routingPartitionSize(3)
.numberOfReplicas(1)
.build();
for (Map.Entry<String, Map<String, Integer>> routingIdEntry : routingIdToShard.entrySet()) {
String routing = routingIdEntry.getKey();
for (Map.Entry<String, Integer> idEntry : routingIdEntry.getValue().entrySet()) {
String id = idEntry.getKey();
int shard = idEntry.getValue();
assertEquals(shard, OperationRouting.generateShardId(metaData, id, routing));
}
}
}
public void testPartitionedIndexBWC() {
Map<String, Map<String, Integer>> routingIdToShard = new HashMap<>();
Map<String, Integer> routingA = new HashMap<>();
routingA.put("a_0", 3);
routingA.put("a_1", 2);
routingA.put("a_2", 2);
routingA.put("a_3", 3);
routingIdToShard.put("a", routingA);
Map<String, Integer> routingB = new HashMap<>();
routingB.put("b_0", 5);
routingB.put("b_1", 0);
routingB.put("b_2", 0);
routingB.put("b_3", 0);
routingIdToShard.put("b", routingB);
Map<String, Integer> routingC = new HashMap<>();
routingC.put("c_0", 4);
routingC.put("c_1", 4);
routingC.put("c_2", 3);
routingC.put("c_3", 4);
routingIdToShard.put("c", routingC);
Map<String, Integer> routingD = new HashMap<>();
routingD.put("d_0", 3);
routingD.put("d_1", 4);
routingD.put("d_2", 4);
routingD.put("d_3", 4);
routingIdToShard.put("d", routingD);
IndexMetaData metaData = IndexMetaData.builder("test")
.settings(settings(Version.CURRENT))
.numberOfShards(6)
.routingPartitionSize(2)
.numberOfReplicas(1)
.build();
for (Map.Entry<String, Map<String, Integer>> routingIdEntry : routingIdToShard.entrySet()) {
String routing = routingIdEntry.getKey();
for (Map.Entry<String, Integer> idEntry : routingIdEntry.getValue().entrySet()) {
String id = idEntry.getKey();
int shard = idEntry.getValue();
assertEquals(shard, OperationRouting.generateShardId(metaData, id, routing));
}
}
}
/**
* Ensures that all changes to the hash-function / shard selection are BWC
*/
public void testBWC() {
Map<String, Integer> termToShard = new TreeMap<>();
termToShard.put("sEERfFzPSI", 1);
termToShard.put("cNRiIrjzYd", 7);
termToShard.put("BgfLBXUyWT", 5);
termToShard.put("cnepjZhQnb", 3);
termToShard.put("OKCmuYkeCK", 6);
termToShard.put("OutXGRQUja", 5);
termToShard.put("yCdyocKWou", 1);
termToShard.put("KXuNWWNgVj", 2);
termToShard.put("DGJOYrpESx", 4);
termToShard.put("upLDybdTGs", 5);
termToShard.put("yhZhzCPQby", 1);
termToShard.put("EyCVeiCouA", 1);
termToShard.put("tFyVdQauWR", 6);
termToShard.put("nyeRYDnDQr", 6);
termToShard.put("hswhrppvDH", 0);
termToShard.put("BSiWvDOsNE", 5);
termToShard.put("YHicpFBSaY", 1);
termToShard.put("EquPtdKaBZ", 4);
termToShard.put("rSjLZHCDfT", 5);
termToShard.put("qoZALVcite", 7);
termToShard.put("yDCCPVBiCm", 7);
termToShard.put("ngizYtQgGK", 5);
termToShard.put("FYQRIBcNqz", 0);
termToShard.put("EBzEDAPODe", 2);
termToShard.put("YePigbXgKb", 1);
termToShard.put("PeGJjomyik", 3);
termToShard.put("cyQIvDmyYD", 7);
termToShard.put("yIEfZrYfRk", 5);
termToShard.put("kblouyFUbu", 7);
termToShard.put("xvIGbRiGJF", 3);
termToShard.put("KWimwsREPf", 4);
termToShard.put("wsNavvIcdk", 7);
termToShard.put("xkWaPcCmpT", 0);
termToShard.put("FKKTOnJMDy", 7);
termToShard.put("RuLzobYixn", 2);
termToShard.put("mFohLeFRvF", 4);
termToShard.put("aAMXnamRJg", 7);
termToShard.put("zKBMYJDmBI", 0);
termToShard.put("ElSVuJQQuw", 7);
termToShard.put("pezPtTQAAm", 7);
termToShard.put("zBjjNEjAex", 2);
termToShard.put("PGgHcLNPYX", 7);
termToShard.put("hOkpeQqTDF", 3);
termToShard.put("chZXraUPBH", 7);
termToShard.put("FAIcSmmNXq", 5);
termToShard.put("EZmDicyayC", 0);
termToShard.put("GRIueBeIyL", 7);
termToShard.put("qCChjGZYLp", 3);
termToShard.put("IsSZQwwnUT", 3);
termToShard.put("MGlxLFyyCK", 3);
termToShard.put("YmscwrKSpB", 0);
termToShard.put("czSljcjMop", 5);
termToShard.put("XhfGWwNlng", 1);
termToShard.put("cWpKJjlzgj", 7);
termToShard.put("eDzIfMKbvk", 1);
termToShard.put("WFFWYBfnTb", 0);
termToShard.put("oDdHJxGxja", 7);
termToShard.put("PDOQQqgIKE", 1);
termToShard.put("bGEIEBLATe", 6);
termToShard.put("xpRkJPWVpu", 2);
termToShard.put("kTwZnPEeIi", 2);
termToShard.put("DifcuqSsKk", 1);
termToShard.put("CEmLmljpXe", 5);
termToShard.put("cuNKtLtyJQ", 7);
termToShard.put("yNjiAnxAmt", 5);
termToShard.put("bVDJDCeaFm", 2);
termToShard.put("vdnUhGLFtl", 0);
termToShard.put("LnqSYezXbr", 5);
termToShard.put("EzHgydDCSR", 3);
termToShard.put("ZSKjhJlcpn", 1);
termToShard.put("WRjUoZwtUz", 3);
termToShard.put("RiBbcCdIgk", 4);
termToShard.put("yizTqyjuDn", 4);
termToShard.put("QnFjcpcZUT", 4);
termToShard.put("agYhXYUUpl", 7);
termToShard.put("UOjiTugjNC", 7);
termToShard.put("nICGuWTdfV", 0);
termToShard.put("NrnSmcnUVF", 2);
termToShard.put("ZSzFcbpDqP", 3);
termToShard.put("YOhahLSzzE", 5);
termToShard.put("iWswCilUaT", 1);
termToShard.put("zXAamKsRwj", 2);
termToShard.put("aqGsrUPHFq", 5);
termToShard.put("eDItImYWTS", 1);
termToShard.put("JAYDZMRcpW", 4);
termToShard.put("lmvAaEPflK", 7);
termToShard.put("IKuOwPjKCx", 5);
termToShard.put("schsINzlYB", 1);
termToShard.put("OqbFNxrKrF", 2);
termToShard.put("QrklDfvEJU", 6);
termToShard.put("VLxKRKdLbx", 4);
termToShard.put("imoydNTZhV", 1);
termToShard.put("uFZyTyOMRO", 4);
termToShard.put("nVAZVMPNNx", 3);
termToShard.put("rPIdESYaAO", 5);
termToShard.put("nbZWPWJsIM", 0);
termToShard.put("wRZXPSoEgd", 3);
termToShard.put("nGzpgwsSBc", 4);
termToShard.put("AITyyoyLLs", 4);
IndexMetaData metaData = IndexMetaData.builder("test").settings(settings(Version.CURRENT)).numberOfShards(8)
.numberOfReplicas(1).build();
for (Map.Entry<String, Integer> entry : termToShard.entrySet()) {
String key = entry.getKey();
int shard = randomBoolean() ?
OperationRouting.generateShardId(metaData, key, null) : OperationRouting.generateShardId(metaData, "foobar", key);
assertEquals(shard, entry.getValue().intValue());
}
}
public void testPreferNodes() throws InterruptedException, IOException {
TestThreadPool threadPool = null;
ClusterService clusterService = null;
try {
threadPool = new TestThreadPool("testPreferNodes");
clusterService = ClusterServiceUtils.createClusterService(threadPool);
final String indexName = "test";
ClusterServiceUtils.setState(clusterService, ClusterStateCreationUtils.stateWithActivePrimary(indexName, true, randomInt(8)));
final Index index = clusterService.state().metaData().index(indexName).getIndex();
final List<ShardRouting> shards = clusterService.state().getRoutingNodes().assignedShards(new ShardId(index, 0));
final int count = randomIntBetween(1, shards.size());
int position = 0;
final List<String> nodes = new ArrayList<>();
final List<ShardRouting> expected = new ArrayList<>();
for (int i = 0; i < count; i++) {
if (randomBoolean() && !shards.get(position).initializing()) {
nodes.add(shards.get(position).currentNodeId());
expected.add(shards.get(position));
position++;
} else {
nodes.add("missing_" + i);
}
}
final ShardIterator it =
new OperationRouting(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS))
.getShards(clusterService.state(), indexName, 0, "_prefer_nodes:" + String.join(",", nodes));
final List<ShardRouting> all = new ArrayList<>();
ShardRouting shard;
while ((shard = it.nextOrNull()) != null) {
all.add(shard);
}
final Set<ShardRouting> preferred = new HashSet<>();
preferred.addAll(all.subList(0, expected.size()));
// the preferred shards should be at the front of the list
assertThat(preferred, containsInAnyOrder(expected.toArray()));
// verify all the shards are there
assertThat(all.size(), equalTo(shards.size()));
} finally {
IOUtils.close(clusterService);
terminate(threadPool);
}
}
public void testThatOnlyNodesSupportNodeIds() throws InterruptedException, IOException {
TestThreadPool threadPool = null;
ClusterService clusterService = null;
try {
threadPool = new TestThreadPool("testThatOnlyNodesSupportNodeIds");
clusterService = ClusterServiceUtils.createClusterService(threadPool);
final String indexName = "test";
ClusterServiceUtils.setState(clusterService, ClusterStateCreationUtils.stateWithActivePrimary(indexName, true, randomInt(8)));
final Index index = clusterService.state().metaData().index(indexName).getIndex();
final List<ShardRouting> shards = clusterService.state().getRoutingNodes().assignedShards(new ShardId(index, 0));
final int count = randomIntBetween(1, shards.size());
int position = 0;
final List<String> nodes = new ArrayList<>();
final List<ShardRouting> expected = new ArrayList<>();
for (int i = 0; i < count; i++) {
if (randomBoolean() && !shards.get(position).initializing()) {
nodes.add(shards.get(position).currentNodeId());
expected.add(shards.get(position));
position++;
} else {
nodes.add("missing_" + i);
}
}
if (expected.size() > 0) {
final ShardIterator it =
new OperationRouting(Settings.EMPTY, new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS))
.getShards(clusterService.state(), indexName, 0, "_only_nodes:" + String.join(",", nodes));
final List<ShardRouting> only = new ArrayList<>();
ShardRouting shard;
while ((shard = it.nextOrNull()) != null) {
only.add(shard);
}
assertThat(new HashSet<>(only), equalTo(new HashSet<>(expected)));
} else {
final ClusterService cs = clusterService;
final IllegalArgumentException e = expectThrows(
IllegalArgumentException.class,
() -> new OperationRouting(
Settings.EMPTY,
new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS))
.getShards(cs.state(), indexName, 0, "_only_nodes:" + String.join(",", nodes)));
if (nodes.size() == 1) {
assertThat(
e,
hasToString(containsString(
"no data nodes with criteria [" + String.join(",", nodes) + "] found for shard: [test][0]")));
} else {
assertThat(
e,
hasToString(containsString(
"no data nodes with criterion [" + String.join(",", nodes) + "] found for shard: [test][0]")));
}
}
} finally {
IOUtils.close(clusterService);
terminate(threadPool);
}
}
}