|
@@ -21,8 +21,11 @@ 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.ClusterState;
|
|
|
import org.elasticsearch.cluster.metadata.IndexMetaData;
|
|
|
+import org.elasticsearch.cluster.node.DiscoveryNodes;
|
|
|
import org.elasticsearch.cluster.service.ClusterService;
|
|
|
+import org.elasticsearch.common.Nullable;
|
|
|
import org.elasticsearch.common.settings.ClusterSettings;
|
|
|
import org.elasticsearch.common.settings.Settings;
|
|
|
import org.elasticsearch.index.Index;
|
|
@@ -44,6 +47,7 @@ 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.Matchers.greaterThan;
|
|
|
import static org.hamcrest.object.HasToString.hasToString;
|
|
|
|
|
|
public class OperationRoutingTests extends ESTestCase{
|
|
@@ -367,6 +371,65 @@ public class OperationRoutingTests extends ESTestCase{
|
|
|
terminate(threadPool);
|
|
|
}
|
|
|
}
|
|
|
+
|
|
|
+ public void testFairSessionIdPreferences() throws InterruptedException, IOException {
|
|
|
+ // Ensure that a user session is re-routed back to same nodes for
|
|
|
+ // subsequent searches and that the nodes are selected fairly i.e.
|
|
|
+ // given identically sorted lists of nodes across all shard IDs
|
|
|
+ // each shard ID doesn't pick the same node.
|
|
|
+ final int numIndices = randomIntBetween(1, 3);
|
|
|
+ final int numShards = randomIntBetween(2, 10);
|
|
|
+ final int numReplicas = randomIntBetween(1, 3);
|
|
|
+ final String[] indexNames = new String[numIndices];
|
|
|
+ for (int i = 0; i < numIndices; i++) {
|
|
|
+ indexNames[i] = "test" + i;
|
|
|
+ }
|
|
|
+ ClusterState state = ClusterStateCreationUtils.stateWithAssignedPrimariesAndReplicas(indexNames, numShards, numReplicas);
|
|
|
+ final int numRepeatedSearches = 4;
|
|
|
+ List<ShardRouting> sessionsfirstSearch = null;
|
|
|
+ OperationRouting opRouting = new OperationRouting(Settings.EMPTY,
|
|
|
+ new ClusterSettings(Settings.EMPTY, ClusterSettings.BUILT_IN_CLUSTER_SETTINGS));
|
|
|
+ String sessionKey = randomAlphaOfLength(10);
|
|
|
+ for (int i = 0; i < numRepeatedSearches; i++) {
|
|
|
+ List<ShardRouting> searchedShards = new ArrayList<>(numShards);
|
|
|
+ Set<String> selectedNodes = new HashSet<>(numShards);
|
|
|
+ final GroupShardsIterator<ShardIterator> groupIterator = opRouting.searchShards(state, indexNames, null, sessionKey);
|
|
|
+
|
|
|
+ assertThat("One group per index shard", groupIterator.size(), equalTo(numIndices * numShards));
|
|
|
+ for (ShardIterator shardIterator : groupIterator) {
|
|
|
+ assertThat(shardIterator.size(), equalTo(numReplicas + 1));
|
|
|
+
|
|
|
+ ShardRouting firstChoice = shardIterator.nextOrNull();
|
|
|
+ assertNotNull(firstChoice);
|
|
|
+ ShardRouting duelFirst = duelGetShards(state, firstChoice.shardId(), sessionKey).nextOrNull();
|
|
|
+ assertThat("Regression test failure", duelFirst, equalTo(firstChoice));
|
|
|
+
|
|
|
+ searchedShards.add(firstChoice);
|
|
|
+ selectedNodes.add(firstChoice.currentNodeId());
|
|
|
+ }
|
|
|
+ if (sessionsfirstSearch == null) {
|
|
|
+ sessionsfirstSearch = searchedShards;
|
|
|
+ } else {
|
|
|
+ assertThat("Sessions must reuse same replica choices", searchedShards, equalTo(sessionsfirstSearch));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2 is the bare minimum number of nodes we can reliably expect from
|
|
|
+ // randomized tests in my experiments over thousands of iterations.
|
|
|
+ // Ideally we would test for greater levels of machine utilisation
|
|
|
+ // given a configuration with many nodes but the nature of hash
|
|
|
+ // collisions means we can't always rely on optimal node usage in
|
|
|
+ // all cases.
|
|
|
+ assertThat("Search should use more than one of the nodes", selectedNodes.size(), greaterThan(1));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Regression test for the routing logic - implements same hashing logic
|
|
|
+ private ShardIterator duelGetShards(ClusterState clusterState, ShardId shardId, String sessionId) {
|
|
|
+ final IndexShardRoutingTable indexShard = clusterState.getRoutingTable().shardRoutingTable(shardId.getIndexName(), shardId.getId());
|
|
|
+ int routingHash = Murmur3HashFunction.hash(sessionId);
|
|
|
+ routingHash = 31 * routingHash + indexShard.shardId.hashCode();
|
|
|
+ return indexShard.activeInitializingShardsIt(routingHash);
|
|
|
+ }
|
|
|
|
|
|
public void testThatOnlyNodesSupportNodeIds() throws InterruptedException, IOException {
|
|
|
TestThreadPool threadPool = null;
|