/*
* 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.node.DiscoveryNode;
import org.elasticsearch.cluster.routing.UnassignedInfo.AllocationStatus;
import org.elasticsearch.cluster.routing.allocation.decider.Decision;
import org.elasticsearch.common.io.stream.BytesStreamOutput;
import org.elasticsearch.test.ESTestCase;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptyMap;
import static java.util.Collections.emptySet;
import static org.hamcrest.Matchers.startsWith;
/**
* Unit tests for the {@link AllocateUnassignedDecision} class.
*/
public class AllocateUnassignedDecisionTests extends ESTestCase {
private DiscoveryNode node1 = new DiscoveryNode("node1", buildNewFakeTransportAddress(), emptyMap(), emptySet(), Version.CURRENT);
private DiscoveryNode node2 = new DiscoveryNode("node2", buildNewFakeTransportAddress(), emptyMap(), emptySet(), Version.CURRENT);
public void testDecisionNotTaken() {
AllocateUnassignedDecision allocateUnassignedDecision = AllocateUnassignedDecision.NOT_TAKEN;
assertFalse(allocateUnassignedDecision.isDecisionTaken());
expectThrows(IllegalStateException.class, () -> allocateUnassignedDecision.getAllocationDecision());
expectThrows(IllegalStateException.class, () -> allocateUnassignedDecision.getAllocationStatus());
expectThrows(IllegalStateException.class, () -> allocateUnassignedDecision.getAllocationId());
expectThrows(IllegalStateException.class, () -> allocateUnassignedDecision.getTargetNode());
expectThrows(IllegalStateException.class, () -> allocateUnassignedDecision.getNodeDecisions());
expectThrows(IllegalStateException.class, () -> allocateUnassignedDecision.getExplanation());
}
public void testNoDecision() {
final AllocationStatus allocationStatus = randomFrom(
AllocationStatus.DELAYED_ALLOCATION, AllocationStatus.NO_VALID_SHARD_COPY, AllocationStatus.FETCHING_SHARD_DATA
);
AllocateUnassignedDecision noDecision = AllocateUnassignedDecision.no(allocationStatus, null);
assertTrue(noDecision.isDecisionTaken());
assertEquals(AllocationDecision.fromAllocationStatus(allocationStatus), noDecision.getAllocationDecision());
assertEquals(allocationStatus, noDecision.getAllocationStatus());
if (allocationStatus == AllocationStatus.FETCHING_SHARD_DATA) {
assertEquals("cannot allocate because information about existing shard data is still being retrieved from " +
"some of the nodes", noDecision.getExplanation());
} else if (allocationStatus == AllocationStatus.DELAYED_ALLOCATION) {
assertThat(noDecision.getExplanation(), startsWith("cannot allocate because the cluster is still waiting"));
} else {
assertThat(noDecision.getExplanation(),
startsWith("cannot allocate because a previous copy of the primary shard existed"));
}
assertNull(noDecision.getNodeDecisions());
assertNull(noDecision.getTargetNode());
assertNull(noDecision.getAllocationId());
List<NodeAllocationResult> nodeDecisions = new ArrayList<>();
nodeDecisions.add(new NodeAllocationResult(node1, Decision.NO, 1));
nodeDecisions.add(new NodeAllocationResult(node2, Decision.NO, 2));
final boolean reuseStore = randomBoolean();
noDecision = AllocateUnassignedDecision.no(AllocationStatus.DECIDERS_NO, nodeDecisions, reuseStore);
assertTrue(noDecision.isDecisionTaken());
assertEquals(AllocationDecision.NO, noDecision.getAllocationDecision());
assertEquals(AllocationStatus.DECIDERS_NO, noDecision.getAllocationStatus());
if (reuseStore) {
assertEquals("cannot allocate because allocation is not permitted to any of the nodes that hold an in-sync shard copy",
noDecision.getExplanation());
} else {
assertEquals("cannot allocate because allocation is not permitted to any of the nodes", noDecision.getExplanation());
}
assertEquals(nodeDecisions.stream().sorted().collect(Collectors.toList()), noDecision.getNodeDecisions());
// node1 should be sorted first b/c of better weight ranking
assertEquals("node1", noDecision.getNodeDecisions().iterator().next().getNode().getId());
assertNull(noDecision.getTargetNode());
assertNull(noDecision.getAllocationId());
// test bad values
expectThrows(NullPointerException.class, () -> AllocateUnassignedDecision.no(null, null));
}
public void testThrottleDecision() {
List<NodeAllocationResult> nodeDecisions = new ArrayList<>();
nodeDecisions.add(new NodeAllocationResult(node1, Decision.NO, 1));
nodeDecisions.add(new NodeAllocationResult(node2, Decision.THROTTLE, 2));
AllocateUnassignedDecision throttleDecision = AllocateUnassignedDecision.throttle(nodeDecisions);
assertTrue(throttleDecision.isDecisionTaken());
assertEquals(AllocationDecision.THROTTLED, throttleDecision.getAllocationDecision());
assertEquals(AllocationStatus.DECIDERS_THROTTLED, throttleDecision.getAllocationStatus());
assertThat(throttleDecision.getExplanation(), startsWith("allocation temporarily throttled"));
assertEquals(nodeDecisions.stream().sorted().collect(Collectors.toList()), throttleDecision.getNodeDecisions());
// node2 should be sorted first b/c a THROTTLE is higher than a NO decision
assertEquals("node2", throttleDecision.getNodeDecisions().iterator().next().getNode().getId());
assertNull(throttleDecision.getTargetNode());
assertNull(throttleDecision.getAllocationId());
}
public void testYesDecision() {
List<NodeAllocationResult> nodeDecisions = new ArrayList<>();
nodeDecisions.add(new NodeAllocationResult(node1, Decision.NO, 1));
nodeDecisions.add(new NodeAllocationResult(node2, Decision.YES, 2));
String allocId = randomBoolean() ? "allocId" : null;
AllocateUnassignedDecision yesDecision = AllocateUnassignedDecision.yes(
node2, allocId, nodeDecisions, randomBoolean());
assertTrue(yesDecision.isDecisionTaken());
assertEquals(AllocationDecision.YES, yesDecision.getAllocationDecision());
assertNull(yesDecision.getAllocationStatus());
assertEquals("can allocate the shard", yesDecision.getExplanation());
assertEquals(nodeDecisions.stream().sorted().collect(Collectors.toList()), yesDecision.getNodeDecisions());
assertEquals("node2", yesDecision.getTargetNode().getId());
assertEquals(allocId, yesDecision.getAllocationId());
// node1 should be sorted first b/c YES decisions are the highest
assertEquals("node2", yesDecision.getNodeDecisions().iterator().next().getNode().getId());
}
public void testCachedDecisions() {
List<AllocationStatus> cachableStatuses = Arrays.asList(AllocationStatus.DECIDERS_NO, AllocationStatus.DECIDERS_THROTTLED,
AllocationStatus.NO_VALID_SHARD_COPY, AllocationStatus.FETCHING_SHARD_DATA, AllocationStatus.DELAYED_ALLOCATION);
for (AllocationStatus allocationStatus : cachableStatuses) {
if (allocationStatus == AllocationStatus.DECIDERS_THROTTLED) {
AllocateUnassignedDecision cached = AllocateUnassignedDecision.throttle(null);
AllocateUnassignedDecision another = AllocateUnassignedDecision.throttle(null);
assertSame(cached, another);
AllocateUnassignedDecision notCached = AllocateUnassignedDecision.throttle(new ArrayList<>());
another = AllocateUnassignedDecision.throttle(new ArrayList<>());
assertNotSame(notCached, another);
} else {
AllocateUnassignedDecision cached = AllocateUnassignedDecision.no(allocationStatus, null);
AllocateUnassignedDecision another = AllocateUnassignedDecision.no(allocationStatus, null);
assertSame(cached, another);
AllocateUnassignedDecision notCached = AllocateUnassignedDecision.no(allocationStatus, new ArrayList<>());
another = AllocateUnassignedDecision.no(allocationStatus, new ArrayList<>());
assertNotSame(notCached, another);
}
}
// yes decisions are not precomputed and cached
AllocateUnassignedDecision first = AllocateUnassignedDecision.yes(node1, "abc", emptyList(), randomBoolean());
AllocateUnassignedDecision second = AllocateUnassignedDecision.yes(node1, "abc", emptyList(), randomBoolean());
// same fields for the ShardAllocationDecision, but should be different instances
assertNotSame(first, second);
}
public void testSerialization() throws IOException {
DiscoveryNode node1 = new DiscoveryNode("node1", buildNewFakeTransportAddress(), emptyMap(), emptySet(), Version.CURRENT);
DiscoveryNode node2 = new DiscoveryNode("node2", buildNewFakeTransportAddress(), emptyMap(), emptySet(), Version.CURRENT);
Decision.Type finalDecision = randomFrom(Decision.Type.values());
DiscoveryNode assignedNode = finalDecision == Decision.Type.YES ? node1 : null;
List<NodeAllocationResult> nodeDecisions = new ArrayList<>();
nodeDecisions.add(new NodeAllocationResult(node1, Decision.NO, 2));
nodeDecisions.add(new NodeAllocationResult(node2, finalDecision == Decision.Type.YES ? Decision.YES :
randomFrom(Decision.NO, Decision.THROTTLE, Decision.YES), 1));
AllocateUnassignedDecision decision;
if (finalDecision == Decision.Type.YES) {
decision = AllocateUnassignedDecision.yes(assignedNode, randomBoolean() ? randomAlphaOfLength(5) : null,
nodeDecisions, randomBoolean());
} else {
decision = AllocateUnassignedDecision.no(randomFrom(
AllocationStatus.DELAYED_ALLOCATION, AllocationStatus.NO_VALID_SHARD_COPY, AllocationStatus.FETCHING_SHARD_DATA
), nodeDecisions, randomBoolean());
}
BytesStreamOutput output = new BytesStreamOutput();
decision.writeTo(output);
AllocateUnassignedDecision readDecision = new AllocateUnassignedDecision(output.bytes().streamInput());
assertEquals(decision.getTargetNode(), readDecision.getTargetNode());
assertEquals(decision.getAllocationStatus(), readDecision.getAllocationStatus());
assertEquals(decision.getExplanation(), readDecision.getExplanation());
assertEquals(decision.getNodeDecisions().size(), readDecision.getNodeDecisions().size());
assertEquals(decision.getAllocationId(), readDecision.getAllocationId());
assertEquals(decision.getAllocationDecision(), readDecision.getAllocationDecision());
// node2 should have the highest sort order
assertEquals("node2", readDecision.getNodeDecisions().iterator().next().getNode().getId());
}
}