|  | @@ -19,17 +19,36 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  package org.elasticsearch.action.admin.cluster.allocation;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +import org.apache.lucene.index.CorruptIndexException;
 | 
	
		
			
				|  |  | +import org.elasticsearch.ElasticsearchException;
 | 
	
		
			
				|  |  | +import org.elasticsearch.ExceptionsHelper;
 | 
	
		
			
				|  |  |  import org.elasticsearch.Version;
 | 
	
		
			
				|  |  | +import org.elasticsearch.action.admin.indices.shards.IndicesShardStoresResponse;
 | 
	
		
			
				|  |  | +import org.elasticsearch.cluster.metadata.IndexMetaData;
 | 
	
		
			
				|  |  |  import org.elasticsearch.cluster.node.DiscoveryNode;
 | 
	
		
			
				|  |  | +import org.elasticsearch.cluster.routing.ShardRouting;
 | 
	
		
			
				|  |  | +import org.elasticsearch.cluster.routing.ShardRoutingHelper;
 | 
	
		
			
				|  |  | +import org.elasticsearch.cluster.routing.UnassignedInfo;
 | 
	
		
			
				|  |  |  import org.elasticsearch.cluster.routing.allocation.decider.Decision;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.io.stream.BytesStreamOutput;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.io.stream.StreamInput;
 | 
	
		
			
				|  |  | +import org.elasticsearch.common.settings.Settings;
 | 
	
		
			
				|  |  |  import org.elasticsearch.common.transport.DummyTransportAddress;
 | 
	
		
			
				|  |  | +import org.elasticsearch.common.xcontent.ToXContent;
 | 
	
		
			
				|  |  | +import org.elasticsearch.common.xcontent.XContentBuilder;
 | 
	
		
			
				|  |  | +import org.elasticsearch.common.xcontent.XContentFactory;
 | 
	
		
			
				|  |  | +import org.elasticsearch.index.Index;
 | 
	
		
			
				|  |  |  import org.elasticsearch.index.shard.ShardId;
 | 
	
		
			
				|  |  |  import org.elasticsearch.test.ESTestCase;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +import java.io.IOException;
 | 
	
		
			
				|  |  | +import java.util.Arrays;
 | 
	
		
			
				|  |  | +import java.util.Collections;
 | 
	
		
			
				|  |  |  import java.util.HashMap;
 | 
	
		
			
				|  |  | +import java.util.HashSet;
 | 
	
		
			
				|  |  | +import java.util.List;
 | 
	
		
			
				|  |  |  import java.util.Map;
 | 
	
		
			
				|  |  | +import java.util.Set;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  import static java.util.Collections.emptyMap;
 | 
	
		
			
				|  |  |  import static java.util.Collections.emptySet;
 | 
	
	
		
			
				|  | @@ -39,6 +58,131 @@ import static java.util.Collections.emptySet;
 | 
	
		
			
				|  |  |   */
 | 
	
		
			
				|  |  |  public final class ClusterAllocationExplanationTests extends ESTestCase {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    private Index i = new Index("foo", "uuid");
 | 
	
		
			
				|  |  | +    private ShardRouting primaryShard = ShardRouting.newUnassigned(i, 0, null, true,
 | 
	
		
			
				|  |  | +            new UnassignedInfo(UnassignedInfo.Reason.INDEX_CREATED, "foo"));
 | 
	
		
			
				|  |  | +    private ShardRouting replicaShard = ShardRouting.newUnassigned(i, 0, null, false,
 | 
	
		
			
				|  |  | +            new UnassignedInfo(UnassignedInfo.Reason.INDEX_CREATED, "foo"));
 | 
	
		
			
				|  |  | +    private IndexMetaData indexMetaData = IndexMetaData.builder("foo")
 | 
	
		
			
				|  |  | +            .settings(Settings.builder()
 | 
	
		
			
				|  |  | +                    .put(IndexMetaData.SETTING_VERSION_CREATED, Version.CURRENT)
 | 
	
		
			
				|  |  | +                    .put(IndexMetaData.SETTING_INDEX_UUID, "uuid"))
 | 
	
		
			
				|  |  | +            .putActiveAllocationIds(0, new HashSet<String>(Arrays.asList("aid1", "aid2")))
 | 
	
		
			
				|  |  | +            .numberOfShards(1)
 | 
	
		
			
				|  |  | +            .numberOfReplicas(1)
 | 
	
		
			
				|  |  | +            .build();
 | 
	
		
			
				|  |  | +    private DiscoveryNode node = new DiscoveryNode("node-0", DummyTransportAddress.INSTANCE, emptyMap(), emptySet(), Version.CURRENT);
 | 
	
		
			
				|  |  | +    private static Decision.Multi yesDecision = new Decision.Multi();
 | 
	
		
			
				|  |  | +    private static Decision.Multi noDecision = new Decision.Multi();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    static {
 | 
	
		
			
				|  |  | +        yesDecision.add(Decision.single(Decision.Type.YES, "yes label", "yes please"));
 | 
	
		
			
				|  |  | +        noDecision.add(Decision.single(Decision.Type.NO, "no label", "no thanks"));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private NodeExplanation makeNodeExplanation(boolean primary, boolean isAssigned, boolean hasErr, boolean hasActiveId) {
 | 
	
		
			
				|  |  | +        Float nodeWeight = randomFloat();
 | 
	
		
			
				|  |  | +        Exception e = hasErr ? new ElasticsearchException("stuff's broke, yo") : null;
 | 
	
		
			
				|  |  | +        IndicesShardStoresResponse.StoreStatus storeStatus = new IndicesShardStoresResponse.StoreStatus(node, 42, "eggplant",
 | 
	
		
			
				|  |  | +                IndicesShardStoresResponse.StoreStatus.AllocationStatus.PRIMARY, e);
 | 
	
		
			
				|  |  | +        String assignedNodeId;
 | 
	
		
			
				|  |  | +        if (isAssigned) {
 | 
	
		
			
				|  |  | +            assignedNodeId = "node-0";
 | 
	
		
			
				|  |  | +        } else {
 | 
	
		
			
				|  |  | +            assignedNodeId = "node-9";
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +        Set<String> activeAllocationIds = new HashSet<>();
 | 
	
		
			
				|  |  | +        if (hasActiveId) {
 | 
	
		
			
				|  |  | +            activeAllocationIds.add("eggplant");
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        return TransportClusterAllocationExplainAction.calculateNodeExplanation(primary ? primaryShard : replicaShard,
 | 
	
		
			
				|  |  | +                indexMetaData, node, noDecision, nodeWeight, storeStatus, assignedNodeId, activeAllocationIds);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private void assertExplanations(NodeExplanation ne, String finalExplanation, ClusterAllocationExplanation.FinalDecision finalDecision,
 | 
	
		
			
				|  |  | +                                    ClusterAllocationExplanation.StoreCopy storeCopy) {
 | 
	
		
			
				|  |  | +        assertEquals(finalExplanation, ne.getFinalExplanation());
 | 
	
		
			
				|  |  | +        assertEquals(finalDecision, ne.getFinalDecision());
 | 
	
		
			
				|  |  | +        assertEquals(storeCopy, ne.getStoreCopy());
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void testDecisionAndExplanation() {
 | 
	
		
			
				|  |  | +        Exception e = new IOException("stuff's broke, yo");
 | 
	
		
			
				|  |  | +        Exception corruptE = new CorruptIndexException("stuff's corrupt, yo", "");
 | 
	
		
			
				|  |  | +        Float nodeWeight = randomFloat();
 | 
	
		
			
				|  |  | +        Set<String> activeAllocationIds = new HashSet<>();
 | 
	
		
			
				|  |  | +        activeAllocationIds.add("eggplant");
 | 
	
		
			
				|  |  | +        ShardRouting primaryStartedShard = ShardRouting.newUnassigned(i, 0, null, true,
 | 
	
		
			
				|  |  | +                new UnassignedInfo(UnassignedInfo.Reason.INDEX_REOPENED, "foo"));
 | 
	
		
			
				|  |  | +        assertTrue(primaryStartedShard.allocatedPostIndexCreate(indexMetaData));
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        IndicesShardStoresResponse.StoreStatus storeStatus = new IndicesShardStoresResponse.StoreStatus(node, 42, "eggplant",
 | 
	
		
			
				|  |  | +                IndicesShardStoresResponse.StoreStatus.AllocationStatus.PRIMARY, e);
 | 
	
		
			
				|  |  | +        NodeExplanation ne = TransportClusterAllocationExplainAction.calculateNodeExplanation(primaryShard, indexMetaData, node,
 | 
	
		
			
				|  |  | +                yesDecision, nodeWeight, storeStatus, "", activeAllocationIds);
 | 
	
		
			
				|  |  | +        assertExplanations(ne, "the copy of the shard cannot be read",
 | 
	
		
			
				|  |  | +                ClusterAllocationExplanation.FinalDecision.NO, ClusterAllocationExplanation.StoreCopy.IO_ERROR);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        ne = TransportClusterAllocationExplainAction.calculateNodeExplanation(primaryShard, indexMetaData, node, yesDecision, nodeWeight,
 | 
	
		
			
				|  |  | +                null, "", activeAllocationIds);
 | 
	
		
			
				|  |  | +        assertExplanations(ne, "the shard can be assigned",
 | 
	
		
			
				|  |  | +                ClusterAllocationExplanation.FinalDecision.YES, ClusterAllocationExplanation.StoreCopy.NONE);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        ne = TransportClusterAllocationExplainAction.calculateNodeExplanation(primaryStartedShard, indexMetaData, node, yesDecision,
 | 
	
		
			
				|  |  | +                nodeWeight, null, "", activeAllocationIds);
 | 
	
		
			
				|  |  | +        assertExplanations(ne, "there is no copy of the shard available",
 | 
	
		
			
				|  |  | +                ClusterAllocationExplanation.FinalDecision.NO, ClusterAllocationExplanation.StoreCopy.NONE);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        ne = TransportClusterAllocationExplainAction.calculateNodeExplanation(primaryShard, indexMetaData, node, noDecision, nodeWeight,
 | 
	
		
			
				|  |  | +                null, "", activeAllocationIds);
 | 
	
		
			
				|  |  | +        assertExplanations(ne, "the shard cannot be assigned because one or more allocation decider returns a 'NO' decision",
 | 
	
		
			
				|  |  | +                ClusterAllocationExplanation.FinalDecision.NO, ClusterAllocationExplanation.StoreCopy.NONE);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        storeStatus = new IndicesShardStoresResponse.StoreStatus(node, 42, "eggplant",
 | 
	
		
			
				|  |  | +                IndicesShardStoresResponse.StoreStatus.AllocationStatus.PRIMARY, null);
 | 
	
		
			
				|  |  | +        ne = TransportClusterAllocationExplainAction.calculateNodeExplanation(primaryShard, indexMetaData, node, noDecision, nodeWeight,
 | 
	
		
			
				|  |  | +                storeStatus, "", activeAllocationIds);
 | 
	
		
			
				|  |  | +        assertExplanations(ne, "the shard cannot be assigned because one or more allocation decider returns a 'NO' decision",
 | 
	
		
			
				|  |  | +                ClusterAllocationExplanation.FinalDecision.NO, ClusterAllocationExplanation.StoreCopy.AVAILABLE);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        storeStatus = new IndicesShardStoresResponse.StoreStatus(node, 42, "eggplant",
 | 
	
		
			
				|  |  | +                IndicesShardStoresResponse.StoreStatus.AllocationStatus.PRIMARY, corruptE);
 | 
	
		
			
				|  |  | +        ne = TransportClusterAllocationExplainAction.calculateNodeExplanation(primaryShard, indexMetaData, node, yesDecision, nodeWeight,
 | 
	
		
			
				|  |  | +                storeStatus, "", activeAllocationIds);
 | 
	
		
			
				|  |  | +        assertExplanations(ne, "the copy of the shard is corrupt",
 | 
	
		
			
				|  |  | +                ClusterAllocationExplanation.FinalDecision.NO, ClusterAllocationExplanation.StoreCopy.CORRUPT);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        storeStatus = new IndicesShardStoresResponse.StoreStatus(node, 42, "banana",
 | 
	
		
			
				|  |  | +                IndicesShardStoresResponse.StoreStatus.AllocationStatus.PRIMARY, null);
 | 
	
		
			
				|  |  | +        ne = TransportClusterAllocationExplainAction.calculateNodeExplanation(primaryShard, indexMetaData, node, yesDecision, nodeWeight,
 | 
	
		
			
				|  |  | +                storeStatus, "", activeAllocationIds);
 | 
	
		
			
				|  |  | +        assertExplanations(ne, "the shard can be assigned",
 | 
	
		
			
				|  |  | +                ClusterAllocationExplanation.FinalDecision.YES, ClusterAllocationExplanation.StoreCopy.STALE);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        storeStatus = new IndicesShardStoresResponse.StoreStatus(node, 42, "banana",
 | 
	
		
			
				|  |  | +                IndicesShardStoresResponse.StoreStatus.AllocationStatus.PRIMARY, null);
 | 
	
		
			
				|  |  | +        ne = TransportClusterAllocationExplainAction.calculateNodeExplanation(primaryStartedShard, indexMetaData, node, yesDecision,
 | 
	
		
			
				|  |  | +                nodeWeight, storeStatus, "", activeAllocationIds);
 | 
	
		
			
				|  |  | +        assertExplanations(ne, "the copy of the shard is stale, allocation ids do not match",
 | 
	
		
			
				|  |  | +                ClusterAllocationExplanation.FinalDecision.NO, ClusterAllocationExplanation.StoreCopy.STALE);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        storeStatus = new IndicesShardStoresResponse.StoreStatus(node, 42, "eggplant",
 | 
	
		
			
				|  |  | +                IndicesShardStoresResponse.StoreStatus.AllocationStatus.PRIMARY, null);
 | 
	
		
			
				|  |  | +        ne = TransportClusterAllocationExplainAction.calculateNodeExplanation(primaryShard, indexMetaData, node, yesDecision, nodeWeight,
 | 
	
		
			
				|  |  | +                storeStatus, "node-0", activeAllocationIds);
 | 
	
		
			
				|  |  | +        assertExplanations(ne, "the shard is already assigned to this node",
 | 
	
		
			
				|  |  | +                ClusterAllocationExplanation.FinalDecision.ALREADY_ASSIGNED, ClusterAllocationExplanation.StoreCopy.AVAILABLE);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        storeStatus = new IndicesShardStoresResponse.StoreStatus(node, 42, "eggplant",
 | 
	
		
			
				|  |  | +                IndicesShardStoresResponse.StoreStatus.AllocationStatus.PRIMARY, null);
 | 
	
		
			
				|  |  | +        ne = TransportClusterAllocationExplainAction.calculateNodeExplanation(primaryShard, indexMetaData, node, yesDecision, nodeWeight,
 | 
	
		
			
				|  |  | +                storeStatus, "", activeAllocationIds);
 | 
	
		
			
				|  |  | +        assertExplanations(ne, "the shard can be assigned and the node contains a valid copy of the shard data",
 | 
	
		
			
				|  |  | +                ClusterAllocationExplanation.FinalDecision.YES, ClusterAllocationExplanation.StoreCopy.AVAILABLE);
 | 
	
		
			
				|  |  | +}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      public void testDecisionEquality() {
 | 
	
		
			
				|  |  |          Decision.Multi d = new Decision.Multi();
 | 
	
		
			
				|  |  |          Decision.Multi d2 = new Decision.Multi();
 | 
	
	
		
			
				|  | @@ -53,21 +197,19 @@ public final class ClusterAllocationExplanationTests extends ESTestCase {
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      public void testExplanationSerialization() throws Exception {
 | 
	
		
			
				|  |  |          ShardId shard = new ShardId("test", "uuid", 0);
 | 
	
		
			
				|  |  | -        Map<DiscoveryNode, Decision> nodeToDecisions = new HashMap<>();
 | 
	
		
			
				|  |  | -        Map<DiscoveryNode, Float> nodeToWeight = new HashMap<>();
 | 
	
		
			
				|  |  | -        for (int i = randomIntBetween(2, 5); i > 0; i--) {
 | 
	
		
			
				|  |  | -            DiscoveryNode dn = new DiscoveryNode("node-" + i, DummyTransportAddress.INSTANCE, emptyMap(), emptySet(), Version.CURRENT);
 | 
	
		
			
				|  |  | -            Decision.Multi d = new Decision.Multi();
 | 
	
		
			
				|  |  | -            d.add(Decision.single(Decision.Type.NO, "no label", "because I said no"));
 | 
	
		
			
				|  |  | -            d.add(Decision.single(Decision.Type.YES, "yes label", "yes please"));
 | 
	
		
			
				|  |  | -            d.add(Decision.single(Decision.Type.THROTTLE, "throttle label", "wait a sec"));
 | 
	
		
			
				|  |  | -            nodeToDecisions.put(dn, d);
 | 
	
		
			
				|  |  | -            nodeToWeight.put(dn, randomFloat());
 | 
	
		
			
				|  |  | -        }
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |          long remainingDelay = randomIntBetween(0, 500);
 | 
	
		
			
				|  |  | -        ClusterAllocationExplanation cae = new ClusterAllocationExplanation(shard, true, "assignedNode", null,
 | 
	
		
			
				|  |  | -                nodeToDecisions, nodeToWeight, remainingDelay);
 | 
	
		
			
				|  |  | +        Map<DiscoveryNode, NodeExplanation> nodeExplanations = new HashMap<>(1);
 | 
	
		
			
				|  |  | +        Float nodeWeight = randomFloat();
 | 
	
		
			
				|  |  | +        Set<String> activeAllocationIds = new HashSet<>();
 | 
	
		
			
				|  |  | +        activeAllocationIds.add("eggplant");
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        IndicesShardStoresResponse.StoreStatus storeStatus = new IndicesShardStoresResponse.StoreStatus(node, 42, "eggplant",
 | 
	
		
			
				|  |  | +                IndicesShardStoresResponse.StoreStatus.AllocationStatus.PRIMARY, null);
 | 
	
		
			
				|  |  | +        NodeExplanation ne = TransportClusterAllocationExplainAction.calculateNodeExplanation(primaryShard, indexMetaData, node,
 | 
	
		
			
				|  |  | +                yesDecision, nodeWeight, storeStatus, "", activeAllocationIds);
 | 
	
		
			
				|  |  | +        nodeExplanations.put(ne.getNode(), ne);
 | 
	
		
			
				|  |  | +        ClusterAllocationExplanation cae = new ClusterAllocationExplanation(shard, true,
 | 
	
		
			
				|  |  | +                "assignedNode", remainingDelay, null, nodeExplanations);
 | 
	
		
			
				|  |  |          BytesStreamOutput out = new BytesStreamOutput();
 | 
	
		
			
				|  |  |          cae.writeTo(out);
 | 
	
		
			
				|  |  |          StreamInput in = StreamInput.wrap(out.bytes());
 | 
	
	
		
			
				|  | @@ -77,10 +219,45 @@ public final class ClusterAllocationExplanationTests extends ESTestCase {
 | 
	
		
			
				|  |  |          assertTrue(cae2.isAssigned());
 | 
	
		
			
				|  |  |          assertEquals("assignedNode", cae2.getAssignedNodeId());
 | 
	
		
			
				|  |  |          assertNull(cae2.getUnassignedInfo());
 | 
	
		
			
				|  |  | -        for (Map.Entry<DiscoveryNode, Decision> entry : cae2.getNodeDecisions().entrySet()) {
 | 
	
		
			
				|  |  | -            assertEquals(nodeToDecisions.get(entry.getKey()), entry.getValue());
 | 
	
		
			
				|  |  | +        assertEquals(remainingDelay, cae2.getRemainingDelayMillis());
 | 
	
		
			
				|  |  | +        for (Map.Entry<DiscoveryNode, NodeExplanation> entry : cae2.getNodeExplanations().entrySet()) {
 | 
	
		
			
				|  |  | +            DiscoveryNode node = entry.getKey();
 | 
	
		
			
				|  |  | +            NodeExplanation explanation = entry.getValue();
 | 
	
		
			
				|  |  | +            IndicesShardStoresResponse.StoreStatus status = explanation.getStoreStatus();
 | 
	
		
			
				|  |  | +            assertNotNull(explanation.getStoreStatus());
 | 
	
		
			
				|  |  | +            assertNotNull(explanation.getDecision());
 | 
	
		
			
				|  |  | +            assertEquals(nodeWeight, explanation.getWeight());
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  | -        assertEquals(nodeToWeight, cae2.getNodeWeights());
 | 
	
		
			
				|  |  | -        assertEquals(remainingDelay, cae2.getRemainingDelayNanos());
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    public void testExplanationToXContent() throws Exception {
 | 
	
		
			
				|  |  | +        ShardId shardId = new ShardId("foo", "uuid", 0);
 | 
	
		
			
				|  |  | +        long remainingDelay = 42;
 | 
	
		
			
				|  |  | +        Decision.Multi d = new Decision.Multi();
 | 
	
		
			
				|  |  | +        d.add(Decision.single(Decision.Type.NO, "no label", "because I said no"));
 | 
	
		
			
				|  |  | +        d.add(Decision.single(Decision.Type.YES, "yes label", "yes please"));
 | 
	
		
			
				|  |  | +        d.add(Decision.single(Decision.Type.THROTTLE, "throttle label", "wait a sec"));
 | 
	
		
			
				|  |  | +        Float nodeWeight = 1.5f;
 | 
	
		
			
				|  |  | +        Set<String> allocationIds = new HashSet<>();
 | 
	
		
			
				|  |  | +        allocationIds.add("bar");
 | 
	
		
			
				|  |  | +        IndicesShardStoresResponse.StoreStatus storeStatus = new IndicesShardStoresResponse.StoreStatus(node, 42, "eggplant",
 | 
	
		
			
				|  |  | +                IndicesShardStoresResponse.StoreStatus.AllocationStatus.PRIMARY, new ElasticsearchException("stuff's broke, yo"));
 | 
	
		
			
				|  |  | +        NodeExplanation ne = TransportClusterAllocationExplainAction.calculateNodeExplanation(primaryShard, indexMetaData, node,
 | 
	
		
			
				|  |  | +                d, nodeWeight, storeStatus, "node-0", allocationIds);
 | 
	
		
			
				|  |  | +        Map<DiscoveryNode, NodeExplanation> nodeExplanations = new HashMap<>(1);
 | 
	
		
			
				|  |  | +        nodeExplanations.put(ne.getNode(), ne);
 | 
	
		
			
				|  |  | +        ClusterAllocationExplanation cae = new ClusterAllocationExplanation(shardId, true,
 | 
	
		
			
				|  |  | +                "assignedNode", remainingDelay, null, nodeExplanations);
 | 
	
		
			
				|  |  | +        XContentBuilder builder = XContentFactory.jsonBuilder();
 | 
	
		
			
				|  |  | +        cae.toXContent(builder, ToXContent.EMPTY_PARAMS);
 | 
	
		
			
				|  |  | +        assertEquals("{\"shard\":{\"index\":\"foo\",\"index_uuid\":\"uuid\",\"id\":0,\"primary\":true},\"assigned\":true," +
 | 
	
		
			
				|  |  | +                        "\"assigned_node_id\":\"assignedNode\",\"nodes\":{\"node-0\":{\"node_name\":\"\",\"node_attribute" +
 | 
	
		
			
				|  |  | +                        "s\":{},\"store\":{\"shard_copy\":\"IO_ERROR\",\"store_exception\":\"ElasticsearchException[stuff" +
 | 
	
		
			
				|  |  | +                        "'s broke, yo]\"},\"final_decision\":\"ALREADY_ASSIGNED\",\"final_explanation\":\"the shard is al" +
 | 
	
		
			
				|  |  | +                        "ready assigned to this node\",\"weight\":1.5,\"decisions\":[{\"decider\":\"no label\",\"decision" +
 | 
	
		
			
				|  |  | +                        "\":\"NO\",\"explanation\":\"because I said no\"},{\"decider\":\"yes label\",\"decision\":\"YES\"" +
 | 
	
		
			
				|  |  | +                        ",\"explanation\":\"yes please\"},{\"decider\":\"throttle label\",\"decision\":\"THROTTLE\",\"exp" +
 | 
	
		
			
				|  |  | +                        "lanation\":\"wait a sec\"}]}}}",
 | 
	
		
			
				|  |  | +                builder.string());
 | 
	
		
			
				|  |  |      }
 | 
	
		
			
				|  |  |  }
 |