Add `explain` flag support to the reroute API

By specifying the `explain` flag, an explanation for the reason a
command can or cannot be executed is returned. No allocation commands
are actually performed.

Returns a response similar to:

{
  "state": {...cluster state...},
  "acknowledged": true,
  "explanations" : [ {
    "command" : "cancel",
      "parameters" : {
        "index" : "decide",
        "shard" : 0,
        "node" : "IvpoKRdtRiGrQ_WKtt4_4w",
        "allow_primary" : false
      },
      "decisions" : [ {
        "decider" : "cancel_allocation_command",
        "decision" : "YES",
        "explanation" : "..."
        } ]
     }, {
      "command" : "move",
      "parameters" : {
        "index" : "decide",
        "shard" : 0,
        "from_node" : "IvpoKRdtRiGrQ_WKtt4_4w",
        "to_node" : "IvpoKRdtRiGrQ_WKtt4_4w"
       },
       "decisions" : [ {
         "decider" : "same_shard",
         "decision" : "NO",
         "explanation" : "shard cannot be allocated on same node [IvpoKRdtRiGrQ_WKtt4_4w] it already exists on"
       },
       etc
       ]
  }]
}

also removes AllocationExplanation from cluster state

Closes #2483
Closes #5169
This commit is contained in:
Lee Hinman 2014-01-31 16:50:32 -07:00
parent 8ceb98752d
commit e53a43800e
36 changed files with 744 additions and 201 deletions

View File

@ -11,12 +11,11 @@ Here is a short example of how a simple reroute API call:
[source,js] [source,js]
-------------------------------------------------- --------------------------------------------------
curl -XPOST 'localhost:9200/_cluster/reroute' -d '{ curl -XPOST 'localhost:9200/_cluster/reroute' -d '{
"commands" : [ { "commands" : [ {
"move" : "move" :
{ {
"index" : "test", "shard" : 0, "index" : "test", "shard" : 0,
"from_node" : "node1", "to_node" : "node2" "from_node" : "node1", "to_node" : "node2"
} }
}, },
@ -45,15 +44,18 @@ the request body). This will cause the commands to apply to the current
cluster state, and return the resulting cluster after the commands (and cluster state, and return the resulting cluster after the commands (and
re-balancing) has been applied. re-balancing) has been applied.
The commands supported are: If the `explain` parameter is specified, a detailed explanation of why the
commands could or could not be executed is returned.
`move`:: The commands supported are:
`move`::
Move a started shard from one node to another node. Accepts Move a started shard from one node to another node. Accepts
`index` and `shard` for index name and shard number, `from_node` for the `index` and `shard` for index name and shard number, `from_node` for the
node to move the shard `from`, and `to_node` for the node to move the node to move the shard `from`, and `to_node` for the node to move the
shard to. shard to.
`cancel`:: `cancel`::
Cancel allocation of a shard (or recovery). Accepts `index` Cancel allocation of a shard (or recovery). Accepts `index`
and `shard` for index name and shard number, and `node` for the node to and `shard` for index name and shard number, and `node` for the node to
cancel the shard allocation on. It also accepts `allow_primary` flag to cancel the shard allocation on. It also accepts `allow_primary` flag to
@ -62,7 +64,7 @@ The commands supported are:
from the primary shard by cancelling them and allowing them to be from the primary shard by cancelling them and allowing them to be
reinitialized through the standard reallocation process. reinitialized through the standard reallocation process.
`allocate`:: `allocate`::
Allocate an unassigned shard to a node. Accepts the Allocate an unassigned shard to a node. Accepts the
`index` and `shard` for index name and shard number, and `node` to `index` and `shard` for index name and shard number, and `node` to
allocate the shard to. It also accepts `allow_primary` flag to allocate the shard to. It also accepts `allow_primary` flag to

View File

@ -12,6 +12,10 @@
"type" : "boolean", "type" : "boolean",
"description" : "Simulate the operation only and return the resulting state" "description" : "Simulate the operation only and return the resulting state"
}, },
"explain": {
"type" : "boolean",
"description" : "Return an explanation of why the commands can or cannot be executed"
},
"filter_metadata": { "filter_metadata": {
"type" : "boolean", "type" : "boolean",
"description" : "Don't return cluster state metadata (default: false)" "description" : "Don't return cluster state metadata (default: false)"

View File

@ -0,0 +1,49 @@
setup:
- do:
indices.create:
index: test_index
body:
settings:
number_of_shards: "1"
number_of_replicas: "0"
- do:
cluster.health:
wait_for_status: green
---
"Explain API with empty command list":
- do:
cluster.reroute:
explain: true
dry_run: true
body:
commands: []
- match: {explanations: []}
---
"Explain API for non-existant node & shard":
- do:
cluster.reroute:
explain: true
dry_run: true
body:
commands:
- cancel:
index: test_index
shard: 9
node: node_0
- match: {explanations.0.command: cancel}
- match:
explanations.0.parameters:
index: test_index
shard: 9
node: node_0
allow_primary: false
- match: {explanations.0.decisions.0.decider: cancel_allocation_command}
- match: {explanations.0.decisions.0.decision: "NO"}
- is_true: explanations.0.decisions.0.explanation

View File

@ -20,7 +20,6 @@ setup:
- is_false: metadata - is_false: metadata
- is_false: routing_table - is_false: routing_table
- is_false: routing_nodes - is_false: routing_nodes
- is_false: allocations
- length: { blocks: 0 } - length: { blocks: 0 }
--- ---
@ -41,7 +40,6 @@ setup:
- is_false: metadata - is_false: metadata
- is_false: routing_table - is_false: routing_table
- is_false: routing_nodes - is_false: routing_nodes
- is_false: allocations
- length: { blocks: 1 } - length: { blocks: 1 }
--- ---
@ -55,7 +53,6 @@ setup:
- is_false: metadata - is_false: metadata
- is_false: routing_table - is_false: routing_table
- is_false: routing_nodes - is_false: routing_nodes
- is_false: allocations
--- ---
"Filtering the cluster state by metadata only should work": "Filtering the cluster state by metadata only should work":
@ -68,7 +65,6 @@ setup:
- is_true: metadata - is_true: metadata
- is_false: routing_table - is_false: routing_table
- is_false: routing_nodes - is_false: routing_nodes
- is_false: allocations
--- ---
@ -82,7 +78,6 @@ setup:
- is_false: metadata - is_false: metadata
- is_true: routing_table - is_true: routing_table
- is_true: routing_nodes - is_true: routing_nodes
- is_true: allocations
--- ---
@ -120,7 +115,6 @@ setup:
- is_true: metadata - is_true: metadata
- is_false: routing_table - is_false: routing_table
- is_false: routing_nodes - is_false: routing_nodes
- is_false: allocations
- is_true: metadata.templates.test1 - is_true: metadata.templates.test1
- is_true: metadata.templates.test2 - is_true: metadata.templates.test2
- is_false: metadata.templates.foo - is_false: metadata.templates.foo
@ -160,5 +154,3 @@ setup:
- is_true: metadata - is_true: metadata
- is_true: routing_table - is_true: routing_table
- is_true: routing_nodes - is_true: routing_nodes
- is_true: allocations

View File

@ -20,6 +20,7 @@
package org.elasticsearch.action.admin.cluster.reroute; package org.elasticsearch.action.admin.cluster.reroute;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.ActionRequestValidationException;
import org.elasticsearch.action.support.master.AcknowledgedRequest; import org.elasticsearch.action.support.master.AcknowledgedRequest;
import org.elasticsearch.cluster.routing.allocation.command.AllocationCommand; import org.elasticsearch.cluster.routing.allocation.command.AllocationCommand;
@ -39,6 +40,7 @@ public class ClusterRerouteRequest extends AcknowledgedRequest<ClusterRerouteReq
AllocationCommands commands = new AllocationCommands(); AllocationCommands commands = new AllocationCommands();
boolean dryRun; boolean dryRun;
boolean explain;
public ClusterRerouteRequest() { public ClusterRerouteRequest() {
} }
@ -69,6 +71,23 @@ public class ClusterRerouteRequest extends AcknowledgedRequest<ClusterRerouteReq
return this.dryRun; return this.dryRun;
} }
/**
* Sets the explain flag, which will collect information about the reroute
* request without executing the actions. Similar to dryRun,
* but human-readable.
*/
public ClusterRerouteRequest explain(boolean explain) {
this.explain = explain;
return this;
}
/**
* Returns the current explain flag
*/
public boolean explain() {
return this.explain;
}
/** /**
* Sets the source for the request. * Sets the source for the request.
*/ */
@ -110,6 +129,11 @@ public class ClusterRerouteRequest extends AcknowledgedRequest<ClusterRerouteReq
super.readFrom(in); super.readFrom(in);
commands = AllocationCommands.readFrom(in); commands = AllocationCommands.readFrom(in);
dryRun = in.readBoolean(); dryRun = in.readBoolean();
if (in.getVersion().onOrAfter(Version.V_1_1_0)) {
explain = in.readBoolean();
} else {
explain = false;
}
readTimeout(in); readTimeout(in);
} }
@ -118,6 +142,9 @@ public class ClusterRerouteRequest extends AcknowledgedRequest<ClusterRerouteReq
super.writeTo(out); super.writeTo(out);
AllocationCommands.writeTo(commands, out); AllocationCommands.writeTo(commands, out);
out.writeBoolean(dryRun); out.writeBoolean(dryRun);
if (out.getVersion().onOrAfter(Version.V_1_1_0)) {
out.writeBoolean(explain);
}
writeTimeout(out); writeTimeout(out);
} }
} }

View File

@ -53,6 +53,15 @@ public class ClusterRerouteRequestBuilder extends AcknowledgedRequestBuilder<Clu
return this; return this;
} }
/**
* Sets the explain flag (defaults to <tt>false</tt>). If true, the
* request will include an explanation in addition to the cluster state.
*/
public ClusterRerouteRequestBuilder setExplain(boolean explain) {
request.explain(explain);
return this;
}
/** /**
* Sets the source for the request * Sets the source for the request
*/ */

View File

@ -19,8 +19,11 @@
package org.elasticsearch.action.admin.cluster.reroute; package org.elasticsearch.action.admin.cluster.reroute;
import org.elasticsearch.Version;
import org.elasticsearch.action.support.master.AcknowledgedResponse; import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.routing.allocation.RoutingExplanations;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
@ -32,14 +35,16 @@ import java.io.IOException;
public class ClusterRerouteResponse extends AcknowledgedResponse { public class ClusterRerouteResponse extends AcknowledgedResponse {
private ClusterState state; private ClusterState state;
private RoutingExplanations explanations;
ClusterRerouteResponse() { ClusterRerouteResponse() {
} }
ClusterRerouteResponse(boolean acknowledged, ClusterState state) { ClusterRerouteResponse(boolean acknowledged, ClusterState state, RoutingExplanations explanations) {
super(acknowledged); super(acknowledged);
this.state = state; this.state = state;
this.explanations = explanations;
} }
/** /**
@ -49,11 +54,20 @@ public class ClusterRerouteResponse extends AcknowledgedResponse {
return this.state; return this.state;
} }
public RoutingExplanations getExplanations() {
return this.explanations;
}
@Override @Override
public void readFrom(StreamInput in) throws IOException { public void readFrom(StreamInput in) throws IOException {
super.readFrom(in); super.readFrom(in);
state = ClusterState.Builder.readFrom(in, null); state = ClusterState.Builder.readFrom(in, null);
readAcknowledged(in); readAcknowledged(in);
if (in.getVersion().onOrAfter(Version.V_1_1_0)) {
explanations = RoutingExplanations.readFrom(in);
} else {
explanations = new RoutingExplanations();
}
} }
@Override @Override
@ -61,5 +75,8 @@ public class ClusterRerouteResponse extends AcknowledgedResponse {
super.writeTo(out); super.writeTo(out);
ClusterState.Builder.writeTo(state, out); ClusterState.Builder.writeTo(state, out);
writeAcknowledged(out); writeAcknowledged(out);
if (out.getVersion().onOrAfter(Version.V_1_1_0)) {
RoutingExplanations.writeTo(explanations, out);
}
} }
} }

View File

@ -26,6 +26,7 @@ import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
import org.elasticsearch.cluster.ClusterService; import org.elasticsearch.cluster.ClusterService;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.routing.allocation.RoutingExplanations;
import org.elasticsearch.cluster.routing.allocation.AllocationService; import org.elasticsearch.cluster.routing.allocation.AllocationService;
import org.elasticsearch.cluster.routing.allocation.RoutingAllocation; import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
@ -75,6 +76,7 @@ public class TransportClusterRerouteAction extends TransportMasterNodeOperationA
clusterService.submitStateUpdateTask("cluster_reroute (api)", Priority.URGENT, new AckedClusterStateUpdateTask() { clusterService.submitStateUpdateTask("cluster_reroute (api)", Priority.URGENT, new AckedClusterStateUpdateTask() {
private volatile ClusterState clusterStateToSend; private volatile ClusterState clusterStateToSend;
private volatile RoutingExplanations explanations;
@Override @Override
public boolean mustAck(DiscoveryNode discoveryNode) { public boolean mustAck(DiscoveryNode discoveryNode) {
@ -83,12 +85,12 @@ public class TransportClusterRerouteAction extends TransportMasterNodeOperationA
@Override @Override
public void onAllNodesAcked(@Nullable Throwable t) { public void onAllNodesAcked(@Nullable Throwable t) {
listener.onResponse(new ClusterRerouteResponse(true, clusterStateToSend)); listener.onResponse(new ClusterRerouteResponse(true, clusterStateToSend, explanations));
} }
@Override @Override
public void onAckTimeout() { public void onAckTimeout() {
listener.onResponse(new ClusterRerouteResponse(false, clusterStateToSend)); listener.onResponse(new ClusterRerouteResponse(false, clusterStateToSend, new RoutingExplanations()));
} }
@Override @Override
@ -109,9 +111,10 @@ public class TransportClusterRerouteAction extends TransportMasterNodeOperationA
@Override @Override
public ClusterState execute(ClusterState currentState) { public ClusterState execute(ClusterState currentState) {
RoutingAllocation.Result routingResult = allocationService.reroute(currentState, request.commands, true); RoutingAllocation.Result routingResult = allocationService.reroute(currentState, request.commands, request.explain());
ClusterState newState = ClusterState.builder(currentState).routingResult(routingResult).build(); ClusterState newState = ClusterState.builder(currentState).routingResult(routingResult).build();
clusterStateToSend = newState; clusterStateToSend = newState;
explanations = routingResult.explanations();
if (request.dryRun) { if (request.dryRun) {
return currentState; return currentState;
} }

View File

@ -90,7 +90,6 @@ public class TransportClusterStateAction extends TransportMasterNodeReadOperatio
} else { } else {
builder.routingTable(currentState.routingTable()); builder.routingTable(currentState.routingTable());
} }
builder.allocationExplanation(currentState.allocationExplanation());
} }
if (request.blocks()) { if (request.blocks()) {
builder.blocks(currentState.blocks()); builder.blocks(currentState.blocks());

View File

@ -23,6 +23,7 @@ import com.carrotsearch.hppc.cursors.ObjectCursor;
import com.carrotsearch.hppc.cursors.ObjectObjectCursor; import com.carrotsearch.hppc.cursors.ObjectObjectCursor;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import org.elasticsearch.ElasticsearchIllegalArgumentException; import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.block.ClusterBlock; import org.elasticsearch.cluster.block.ClusterBlock;
import org.elasticsearch.cluster.block.ClusterBlocks; import org.elasticsearch.cluster.block.ClusterBlocks;
import org.elasticsearch.cluster.metadata.IndexMetaData; import org.elasticsearch.cluster.metadata.IndexMetaData;
@ -48,10 +49,12 @@ import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory; import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.shard.ShardId;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
/** /**
* *
@ -105,8 +108,6 @@ public class ClusterState implements ToXContent {
private final ClusterBlocks blocks; private final ClusterBlocks blocks;
private final AllocationExplanation allocationExplanation;
private final ImmutableOpenMap<String, Custom> customs; private final ImmutableOpenMap<String, Custom> customs;
// built on demand // built on demand
@ -115,16 +116,15 @@ public class ClusterState implements ToXContent {
private SettingsFilter settingsFilter; private SettingsFilter settingsFilter;
public ClusterState(long version, ClusterState state) { public ClusterState(long version, ClusterState state) {
this(version, state.metaData(), state.routingTable(), state.nodes(), state.blocks(), state.allocationExplanation(), state.customs()); this(version, state.metaData(), state.routingTable(), state.nodes(), state.blocks(), state.customs());
} }
public ClusterState(long version, MetaData metaData, RoutingTable routingTable, DiscoveryNodes nodes, ClusterBlocks blocks, AllocationExplanation allocationExplanation, ImmutableOpenMap<String, Custom> customs) { public ClusterState(long version, MetaData metaData, RoutingTable routingTable, DiscoveryNodes nodes, ClusterBlocks blocks, ImmutableOpenMap<String, Custom> customs) {
this.version = version; this.version = version;
this.metaData = metaData; this.metaData = metaData;
this.routingTable = routingTable; this.routingTable = routingTable;
this.nodes = nodes; this.nodes = nodes;
this.blocks = blocks; this.blocks = blocks;
this.allocationExplanation = allocationExplanation;
this.customs = customs; this.customs = customs;
} }
@ -176,14 +176,6 @@ public class ClusterState implements ToXContent {
return blocks; return blocks;
} }
public AllocationExplanation allocationExplanation() {
return this.allocationExplanation;
}
public AllocationExplanation getAllocationExplanation() {
return allocationExplanation();
}
public ImmutableOpenMap<String, Custom> customs() { public ImmutableOpenMap<String, Custom> customs() {
return this.customs; return this.customs;
} }
@ -417,28 +409,6 @@ public class ClusterState implements ToXContent {
builder.endObject(); builder.endObject();
} }
if (isAllMetricsOnly || metrics.contains("routing_table")) {
builder.startArray("allocations");
for (Map.Entry<ShardId, List<AllocationExplanation.NodeExplanation>> entry : allocationExplanation().explanations().entrySet()) {
builder.startObject();
builder.field("index", entry.getKey().index().name());
builder.field("shard", entry.getKey().id());
builder.startArray("explanations");
for (AllocationExplanation.NodeExplanation nodeExplanation : entry.getValue()) {
builder.field("desc", nodeExplanation.description());
if (nodeExplanation.node() != null) {
builder.startObject("node");
builder.field("id", nodeExplanation.node().id());
builder.field("name", nodeExplanation.node().name());
builder.endObject();
}
}
builder.endArray();
builder.endObject();
}
builder.endArray();
}
if (isAllMetricsOnly || metrics.contains("customs")) { if (isAllMetricsOnly || metrics.contains("customs")) {
for (ObjectObjectCursor<String, Custom> cursor : customs) { for (ObjectObjectCursor<String, Custom> cursor : customs) {
builder.startObject(cursor.key); builder.startObject(cursor.key);
@ -465,7 +435,6 @@ public class ClusterState implements ToXContent {
private RoutingTable routingTable = RoutingTable.EMPTY_ROUTING_TABLE; private RoutingTable routingTable = RoutingTable.EMPTY_ROUTING_TABLE;
private DiscoveryNodes nodes = DiscoveryNodes.EMPTY_NODES; private DiscoveryNodes nodes = DiscoveryNodes.EMPTY_NODES;
private ClusterBlocks blocks = ClusterBlocks.EMPTY_CLUSTER_BLOCK; private ClusterBlocks blocks = ClusterBlocks.EMPTY_CLUSTER_BLOCK;
private AllocationExplanation allocationExplanation = AllocationExplanation.EMPTY;
private final ImmutableOpenMap.Builder<String, Custom> customs; private final ImmutableOpenMap.Builder<String, Custom> customs;
public Builder() { public Builder() {
@ -478,7 +447,6 @@ public class ClusterState implements ToXContent {
this.routingTable = state.routingTable(); this.routingTable = state.routingTable();
this.metaData = state.metaData(); this.metaData = state.metaData();
this.blocks = state.blocks(); this.blocks = state.blocks();
this.allocationExplanation = state.allocationExplanation();
this.customs = ImmutableOpenMap.builder(state.customs()); this.customs = ImmutableOpenMap.builder(state.customs());
} }
@ -497,7 +465,6 @@ public class ClusterState implements ToXContent {
public Builder routingResult(RoutingAllocation.Result routingResult) { public Builder routingResult(RoutingAllocation.Result routingResult) {
this.routingTable = routingResult.routingTable(); this.routingTable = routingResult.routingTable();
this.allocationExplanation = routingResult.explanation();
return this; return this;
} }
@ -524,11 +491,6 @@ public class ClusterState implements ToXContent {
return this; return this;
} }
public Builder allocationExplanation(AllocationExplanation allocationExplanation) {
this.allocationExplanation = allocationExplanation;
return this;
}
public Builder version(long version) { public Builder version(long version) {
this.version = version; this.version = version;
return this; return this;
@ -549,7 +511,7 @@ public class ClusterState implements ToXContent {
} }
public ClusterState build() { public ClusterState build() {
return new ClusterState(version, metaData, routingTable, nodes, blocks, allocationExplanation, customs.build()); return new ClusterState(version, metaData, routingTable, nodes, blocks, customs.build());
} }
public static byte[] toBytes(ClusterState state) throws IOException { public static byte[] toBytes(ClusterState state) throws IOException {
@ -568,7 +530,10 @@ public class ClusterState implements ToXContent {
RoutingTable.Builder.writeTo(state.routingTable(), out); RoutingTable.Builder.writeTo(state.routingTable(), out);
DiscoveryNodes.Builder.writeTo(state.nodes(), out); DiscoveryNodes.Builder.writeTo(state.nodes(), out);
ClusterBlocks.Builder.writeClusterBlocks(state.blocks(), out); ClusterBlocks.Builder.writeClusterBlocks(state.blocks(), out);
state.allocationExplanation().writeTo(out); if (out.getVersion().before(Version.V_1_1_0)) {
// Versions before 1.1.0 are expecting AllocationExplanation
AllocationExplanation.EMPTY.writeTo(out);
}
out.writeVInt(state.customs().size()); out.writeVInt(state.customs().size());
for (ObjectObjectCursor<String, Custom> cursor : state.customs()) { for (ObjectObjectCursor<String, Custom> cursor : state.customs()) {
out.writeString(cursor.key); out.writeString(cursor.key);
@ -583,7 +548,10 @@ public class ClusterState implements ToXContent {
builder.routingTable = RoutingTable.Builder.readFrom(in); builder.routingTable = RoutingTable.Builder.readFrom(in);
builder.nodes = DiscoveryNodes.Builder.readFrom(in, localNode); builder.nodes = DiscoveryNodes.Builder.readFrom(in, localNode);
builder.blocks = ClusterBlocks.Builder.readClusterBlocks(in); builder.blocks = ClusterBlocks.Builder.readClusterBlocks(in);
builder.allocationExplanation = AllocationExplanation.readAllocationExplanation(in); if (in.getVersion().before(Version.V_1_1_0)) {
// Ignore the explanation read, since after 1.1.0 it's not part of the cluster state
AllocationExplanation.readAllocationExplanation(in);
}
int customSize = in.readVInt(); int customSize = in.readVInt();
for (int i = 0; i < customSize; i++) { for (int i = 0; i < customSize; i++) {
String type = in.readString(); String type = in.readString();

View File

@ -79,13 +79,13 @@ public class AllocationService extends AbstractComponent {
StartedRerouteAllocation allocation = new StartedRerouteAllocation(allocationDeciders, routingNodes, clusterState.nodes(), startedShards, clusterInfoService.getClusterInfo()); StartedRerouteAllocation allocation = new StartedRerouteAllocation(allocationDeciders, routingNodes, clusterState.nodes(), startedShards, clusterInfoService.getClusterInfo());
boolean changed = applyStartedShards(routingNodes, startedShards); boolean changed = applyStartedShards(routingNodes, startedShards);
if (!changed) { if (!changed) {
return new RoutingAllocation.Result(false, clusterState.routingTable(), allocation.explanation()); return new RoutingAllocation.Result(false, clusterState.routingTable());
} }
shardsAllocators.applyStartedShards(allocation); shardsAllocators.applyStartedShards(allocation);
if (withReroute) { if (withReroute) {
reroute(allocation); reroute(allocation);
} }
return new RoutingAllocation.Result(true, new RoutingTable.Builder().updateNodes(routingNodes).build().validateRaiseException(clusterState.metaData()), allocation.explanation()); return new RoutingAllocation.Result(true, new RoutingTable.Builder().updateNodes(routingNodes).build().validateRaiseException(clusterState.metaData()));
} }
public RoutingAllocation.Result applyFailedShard(ClusterState clusterState, ShardRouting failedShard) { public RoutingAllocation.Result applyFailedShard(ClusterState clusterState, ShardRouting failedShard) {
@ -107,33 +107,34 @@ public class AllocationService extends AbstractComponent {
changed |= applyFailedShard(allocation, failedShard, true); changed |= applyFailedShard(allocation, failedShard, true);
} }
if (!changed) { if (!changed) {
return new RoutingAllocation.Result(false, clusterState.routingTable(), allocation.explanation()); return new RoutingAllocation.Result(false, clusterState.routingTable());
} }
shardsAllocators.applyFailedShards(allocation); shardsAllocators.applyFailedShards(allocation);
reroute(allocation); reroute(allocation);
return new RoutingAllocation.Result(true, new RoutingTable.Builder().updateNodes(routingNodes).build().validateRaiseException(clusterState.metaData()), allocation.explanation()); return new RoutingAllocation.Result(true, new RoutingTable.Builder().updateNodes(routingNodes).build().validateRaiseException(clusterState.metaData()));
} }
public RoutingAllocation.Result reroute(ClusterState clusterState, AllocationCommands commands) { public RoutingAllocation.Result reroute(ClusterState clusterState, AllocationCommands commands) {
return reroute(clusterState, commands, false); return reroute(clusterState, commands, false);
} }
public RoutingAllocation.Result reroute(ClusterState clusterState, AllocationCommands commands, boolean debug) throws ElasticsearchException { public RoutingAllocation.Result reroute(ClusterState clusterState, AllocationCommands commands, boolean explain) throws ElasticsearchException {
RoutingNodes routingNodes = clusterState.routingNodes(); RoutingNodes routingNodes = clusterState.routingNodes();
// we don't shuffle the unassigned shards here, to try and get as close as possible to // we don't shuffle the unassigned shards here, to try and get as close as possible to
// a consistent result of the effect the commands have on the routing // a consistent result of the effect the commands have on the routing
// this allows systems to dry run the commands, see the resulting cluster state, and act on it // this allows systems to dry run the commands, see the resulting cluster state, and act on it
RoutingAllocation allocation = new RoutingAllocation(allocationDeciders, routingNodes, clusterState.nodes(), clusterInfoService.getClusterInfo()); RoutingAllocation allocation = new RoutingAllocation(allocationDeciders, routingNodes, clusterState.nodes(), clusterInfoService.getClusterInfo());
allocation.debugDecision(debug); // don't short circuit deciders, we want a full explanation
allocation.debugDecision(true);
// we ignore disable allocation, because commands are explicit // we ignore disable allocation, because commands are explicit
allocation.ignoreDisable(true); allocation.ignoreDisable(true);
commands.execute(allocation); RoutingExplanations explanations = commands.execute(allocation, explain);
// we revert the ignore disable flag, since when rerouting, we want the original setting to take place // we revert the ignore disable flag, since when rerouting, we want the original setting to take place
allocation.ignoreDisable(false); allocation.ignoreDisable(false);
// the assumption is that commands will move / act on shards (or fail through exceptions) // the assumption is that commands will move / act on shards (or fail through exceptions)
// so, there will always be shard "movements", so no need to check on reroute // so, there will always be shard "movements", so no need to check on reroute
reroute(allocation); reroute(allocation);
return new RoutingAllocation.Result(true, new RoutingTable.Builder().updateNodes(routingNodes).build().validateRaiseException(clusterState.metaData()), allocation.explanation()); return new RoutingAllocation.Result(true, new RoutingTable.Builder().updateNodes(routingNodes).build().validateRaiseException(clusterState.metaData()), explanations);
} }
/** /**
@ -157,9 +158,9 @@ public class AllocationService extends AbstractComponent {
RoutingAllocation allocation = new RoutingAllocation(allocationDeciders, routingNodes, clusterState.nodes(), clusterInfoService.getClusterInfo()); RoutingAllocation allocation = new RoutingAllocation(allocationDeciders, routingNodes, clusterState.nodes(), clusterInfoService.getClusterInfo());
allocation.debugDecision(debug); allocation.debugDecision(debug);
if (!reroute(allocation)) { if (!reroute(allocation)) {
return new RoutingAllocation.Result(false, clusterState.routingTable(), allocation.explanation()); return new RoutingAllocation.Result(false, clusterState.routingTable());
} }
return new RoutingAllocation.Result(true, new RoutingTable.Builder().updateNodes(routingNodes).build().validateRaiseException(clusterState.metaData()), allocation.explanation()); return new RoutingAllocation.Result(true, new RoutingTable.Builder().updateNodes(routingNodes).build().validateRaiseException(clusterState.metaData()));
} }
/** /**
@ -194,9 +195,9 @@ public class AllocationService extends AbstractComponent {
changed |= electPrimariesAndUnassignDanglingReplicas(allocation); changed |= electPrimariesAndUnassignDanglingReplicas(allocation);
if (!changed) { if (!changed) {
return new RoutingAllocation.Result(false, clusterState.routingTable(), allocation.explanation()); return new RoutingAllocation.Result(false, clusterState.routingTable());
} }
return new RoutingAllocation.Result(true, new RoutingTable.Builder().updateNodes(routingNodes).build().validateRaiseException(clusterState.metaData()), allocation.explanation()); return new RoutingAllocation.Result(true, new RoutingTable.Builder().updateNodes(routingNodes).build().validateRaiseException(clusterState.metaData()));
} }
private boolean reroute(RoutingAllocation allocation) { private boolean reroute(RoutingAllocation allocation) {

View File

@ -0,0 +1,84 @@
/*
* 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.cluster.routing.allocation.command.AllocationCommand;
import org.elasticsearch.cluster.routing.allocation.command.AllocationCommands;
import org.elasticsearch.cluster.routing.allocation.decider.Decision;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
/**
* Class encapsulating the explanation for a single {@link AllocationCommand}
* taken from the Deciders
*/
public class RerouteExplanation implements ToXContent {
private AllocationCommand command;
private Decision decisions;
public RerouteExplanation(AllocationCommand command, Decision decisions) {
this.command = command;
this.decisions = decisions;
}
public AllocationCommand command() {
return this.command;
}
public Decision decisions() {
return this.decisions;
}
public static RerouteExplanation readFrom(StreamInput in) throws IOException {
String commandName = in.readString();
AllocationCommand command = AllocationCommands.lookupFactorySafe(commandName).readFrom(in);
Decision decisions = Decision.readFrom(in);
return new RerouteExplanation(command, decisions);
}
public static void writeTo(RerouteExplanation explanation, StreamOutput out) throws IOException {
out.writeString(explanation.command.name());
AllocationCommands.lookupFactorySafe(explanation.command.name()).writeTo(explanation.command, out);
Decision.writeTo(explanation.decisions, out);
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("command", command.name());
AllocationCommands.lookupFactorySafe(command.name()).toXContent(command, builder, params, "parameters");
// The Decision could be a Multi or Single decision, and they should
// both be encoded the same, so check and wrap in an array if necessary
if (decisions instanceof Decision.Multi) {
decisions.toXContent(builder, params);
} else {
builder.startArray("decisions");
decisions.toXContent(builder, params);
builder.endArray();
}
builder.endObject();
return builder;
}
}

View File

@ -47,19 +47,30 @@ public class RoutingAllocation {
private final RoutingTable routingTable; private final RoutingTable routingTable;
private final AllocationExplanation explanation; private RoutingExplanations explanations = new RoutingExplanations();
/**
* Creates a new {@link RoutingAllocation.Result}
*
* @param changed a flag to determine whether the actual {@link RoutingTable} has been changed
* @param routingTable the {@link RoutingTable} this Result references
*/
public Result(boolean changed, RoutingTable routingTable) {
this.changed = changed;
this.routingTable = routingTable;
}
/** /**
* Creates a new {@link RoutingAllocation.Result} * Creates a new {@link RoutingAllocation.Result}
* *
* @param changed a flag to determine whether the actual {@link RoutingTable} has been changed * @param changed a flag to determine whether the actual {@link RoutingTable} has been changed
* @param routingTable the {@link RoutingTable} this Result references * @param routingTable the {@link RoutingTable} this Result references
* @param explanation Explanation of the Result * @param explanations Explanation for the reroute actions
*/ */
public Result(boolean changed, RoutingTable routingTable, AllocationExplanation explanation) { public Result(boolean changed, RoutingTable routingTable, RoutingExplanations explanations) {
this.changed = changed; this.changed = changed;
this.routingTable = routingTable; this.routingTable = routingTable;
this.explanation = explanation; this.explanations = explanations;
} }
/** determine whether the actual {@link RoutingTable} has been changed /** determine whether the actual {@link RoutingTable} has been changed
@ -81,8 +92,8 @@ public class RoutingAllocation {
* Get the explanation of this result * Get the explanation of this result
* @return explanation * @return explanation
*/ */
public AllocationExplanation explanation() { public RoutingExplanations explanations() {
return explanation; return explanations;
} }
} }
@ -198,10 +209,14 @@ public class RoutingAllocation {
/** /**
* Create a routing decision, including the reason if the debug flag is * Create a routing decision, including the reason if the debug flag is
* turned on * turned on
* @param decision decision whether to allow/deny allocation
* @param deciderLabel a human readable label for the AllocationDecider
* @param reason a format string explanation of the decision
* @param params format string parameters
*/ */
public Decision decision(Decision decision, String reason, Object... params) { public Decision decision(Decision decision, String deciderLabel, String reason, Object... params) {
if (debugDecision()) { if (debugDecision()) {
return Decision.single(decision.type(), reason, params); return Decision.single(decision.type(), deciderLabel, reason, params);
} else { } else {
return decision; return decision;
} }

View File

@ -0,0 +1,84 @@
/*
* 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.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.List;
import static com.google.common.collect.Lists.newArrayList;
/**
* Class used to encapsulate a number of {@link RerouteExplanation}
* explanations.
*/
public class RoutingExplanations implements ToXContent {
private final List<RerouteExplanation> explanations;
public RoutingExplanations() {
this.explanations = newArrayList();
}
public RoutingExplanations add(RerouteExplanation explanation) {
this.explanations.add(explanation);
return this;
}
public List<RerouteExplanation> explanations() {
return this.explanations;
}
/**
* Read in a RoutingExplanations object
*/
public static RoutingExplanations readFrom(StreamInput in) throws IOException {
int exCount = in.readVInt();
RoutingExplanations exp = new RoutingExplanations();
for (int i = 0; i < exCount; i++) {
RerouteExplanation explanation = RerouteExplanation.readFrom(in);
exp.add(explanation);
}
return exp;
}
/**
* Write the RoutingExplanations object
*/
public static void writeTo(RoutingExplanations explanations, StreamOutput out) throws IOException {
out.writeVInt(explanations.explanations.size());
for (RerouteExplanation explanation : explanations.explanations) {
RerouteExplanation.writeTo(explanation, out);
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startArray("explanations");
for (RerouteExplanation explanation : explanations) {
explanation.toXContent(builder, params);
}
builder.endArray();
return builder;
}
}

View File

@ -26,6 +26,7 @@ import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.routing.MutableShardRouting; import org.elasticsearch.cluster.routing.MutableShardRouting;
import org.elasticsearch.cluster.routing.RoutingNode; import org.elasticsearch.cluster.routing.RoutingNode;
import org.elasticsearch.cluster.routing.allocation.RerouteExplanation;
import org.elasticsearch.cluster.routing.allocation.RoutingAllocation; import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
import org.elasticsearch.cluster.routing.allocation.decider.Decision; import org.elasticsearch.cluster.routing.allocation.decider.Decision;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
@ -102,9 +103,13 @@ public class AllocateAllocationCommand implements AllocationCommand {
} }
@Override @Override
public void toXContent(AllocateAllocationCommand command, XContentBuilder builder, ToXContent.Params params) throws IOException { public void toXContent(AllocateAllocationCommand command, XContentBuilder builder, ToXContent.Params params, String objectName) throws IOException {
builder.startObject(); if (objectName == null) {
builder.field("index", command.shardId().index()); builder.startObject();
} else {
builder.startObject(objectName);
}
builder.field("index", command.shardId().index().name());
builder.field("shard", command.shardId().id()); builder.field("shard", command.shardId().id());
builder.field("node", command.node()); builder.field("node", command.node());
builder.field("allow_primary", command.allowPrimary()); builder.field("allow_primary", command.allowPrimary());
@ -162,7 +167,7 @@ public class AllocateAllocationCommand implements AllocationCommand {
} }
@Override @Override
public void execute(RoutingAllocation allocation) throws ElasticsearchException { public RerouteExplanation execute(RoutingAllocation allocation, boolean explain) throws ElasticsearchException {
DiscoveryNode discoNode = allocation.nodes().resolveNode(node); DiscoveryNode discoNode = allocation.nodes().resolveNode(node);
MutableShardRouting shardRouting = null; MutableShardRouting shardRouting = null;
@ -176,24 +181,43 @@ public class AllocateAllocationCommand implements AllocationCommand {
} }
if (shardRouting == null) { if (shardRouting == null) {
if (explain) {
return new RerouteExplanation(this, allocation.decision(Decision.NO, "allocate_allocation_command",
"failed to find " + shardId + " on the list of unassigned shards"));
}
throw new ElasticsearchIllegalArgumentException("[allocate] failed to find " + shardId + " on the list of unassigned shards"); throw new ElasticsearchIllegalArgumentException("[allocate] failed to find " + shardId + " on the list of unassigned shards");
} }
if (shardRouting.primary() && !allowPrimary) { if (shardRouting.primary() && !allowPrimary) {
throw new ElasticsearchIllegalArgumentException("[allocate] trying to allocate a primary shard " + shardId + "], which is disabled"); if (explain) {
return new RerouteExplanation(this, allocation.decision(Decision.NO, "allocate_allocation_command",
"trying to allocate a primary shard " + shardId + ", which is disabled"));
}
throw new ElasticsearchIllegalArgumentException("[allocate] trying to allocate a primary shard " + shardId + ", which is disabled");
} }
RoutingNode routingNode = allocation.routingNodes().node(discoNode.id()); RoutingNode routingNode = allocation.routingNodes().node(discoNode.id());
if (routingNode == null) { if (routingNode == null) {
if (!discoNode.dataNode()) { if (!discoNode.dataNode()) {
if (explain) {
return new RerouteExplanation(this, allocation.decision(Decision.NO, "allocate_allocation_command",
"Allocation can only be done on data nodes, not [" + node + "]"));
}
throw new ElasticsearchIllegalArgumentException("Allocation can only be done on data nodes, not [" + node + "]"); throw new ElasticsearchIllegalArgumentException("Allocation can only be done on data nodes, not [" + node + "]");
} else { } else {
if (explain) {
return new RerouteExplanation(this, allocation.decision(Decision.NO, "allocate_allocation_command",
"Could not find [" + node + "] among the routing nodes"));
}
throw new ElasticsearchIllegalStateException("Could not find [" + node + "] among the routing nodes"); throw new ElasticsearchIllegalStateException("Could not find [" + node + "] among the routing nodes");
} }
} }
Decision decision = allocation.deciders().canAllocate(shardRouting, routingNode, allocation); Decision decision = allocation.deciders().canAllocate(shardRouting, routingNode, allocation);
if (decision.type() == Decision.Type.NO) { if (decision.type() == Decision.Type.NO) {
if (explain) {
return new RerouteExplanation(this, decision);
}
throw new ElasticsearchIllegalArgumentException("[allocate] allocation of " + shardId + " on node " + discoNode + " is not allowed, reason: " + decision); throw new ElasticsearchIllegalArgumentException("[allocate] allocation of " + shardId + " on node " + discoNode + " is not allowed, reason: " + decision);
} }
// go over and remove it from the unassigned // go over and remove it from the unassigned
@ -210,5 +234,6 @@ public class AllocateAllocationCommand implements AllocationCommand {
} }
break; break;
} }
return new RerouteExplanation(this, decision);
} }
} }

View File

@ -20,7 +20,9 @@
package org.elasticsearch.cluster.routing.allocation.command; package org.elasticsearch.cluster.routing.allocation.command;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.cluster.routing.allocation.RerouteExplanation;
import org.elasticsearch.cluster.routing.allocation.RoutingAllocation; import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
@ -68,10 +70,11 @@ public interface AllocationCommand {
* Writes an {@link AllocationCommand} using an {@link XContentBuilder} * Writes an {@link AllocationCommand} using an {@link XContentBuilder}
* @param command {@link AllocationCommand} to write * @param command {@link AllocationCommand} to write
* @param builder {@link XContentBuilder} to use * @param builder {@link XContentBuilder} to use
* @param params parameters to use when writing the command * @param params parameters to use when writing the command
* @param objectName object the encoding should be encased in, null means a plain object
* @throws IOException if something happens during writing the command * @throws IOException if something happens during writing the command
*/ */
void toXContent(T command, XContentBuilder builder, ToXContent.Params params) throws IOException; void toXContent(T command, XContentBuilder builder, ToXContent.Params params, @Nullable String objectName) throws IOException;
} }
/** /**
@ -85,5 +88,5 @@ public interface AllocationCommand {
* @param allocation {@link RoutingAllocation} to modify * @param allocation {@link RoutingAllocation} to modify
* @throws org.elasticsearch.ElasticsearchException if something happens during reconfiguration * @throws org.elasticsearch.ElasticsearchException if something happens during reconfiguration
*/ */
void execute(RoutingAllocation allocation) throws ElasticsearchException; RerouteExplanation execute(RoutingAllocation allocation, boolean explain) throws ElasticsearchException;
} }

View File

@ -19,10 +19,10 @@
package org.elasticsearch.cluster.routing.allocation.command; package org.elasticsearch.cluster.routing.allocation.command;
import com.google.common.collect.Lists;
import org.elasticsearch.ElasticsearchException; import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.ElasticsearchIllegalArgumentException; import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.cluster.routing.allocation.RoutingExplanations;
import org.elasticsearch.cluster.routing.allocation.RoutingAllocation; import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
@ -37,6 +37,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static com.google.common.collect.Lists.newArrayList;
/** /**
* A simple {@link AllocationCommand} composite managing several * A simple {@link AllocationCommand} composite managing several
* {@link AllocationCommand} implementations * {@link AllocationCommand} implementations
@ -73,7 +75,7 @@ public class AllocationCommands {
registerFactory(MoveAllocationCommand.NAME, new MoveAllocationCommand.Factory()); registerFactory(MoveAllocationCommand.NAME, new MoveAllocationCommand.Factory());
} }
private final List<AllocationCommand> commands = Lists.newArrayList(); private final List<AllocationCommand> commands = newArrayList();
/** /**
* Creates a new set of {@link AllocationCommands} * Creates a new set of {@link AllocationCommands}
@ -111,10 +113,12 @@ public class AllocationCommands {
* @param allocation {@link RoutingAllocation} to apply this command to * @param allocation {@link RoutingAllocation} to apply this command to
* @throws org.elasticsearch.ElasticsearchException if something happens during execution * @throws org.elasticsearch.ElasticsearchException if something happens during execution
*/ */
public void execute(RoutingAllocation allocation) throws ElasticsearchException { public RoutingExplanations execute(RoutingAllocation allocation, boolean explain) throws ElasticsearchException {
RoutingExplanations explanations = new RoutingExplanations();
for (AllocationCommand command : commands) { for (AllocationCommand command : commands) {
command.execute(allocation); explanations.add(command.execute(allocation, explain));
} }
return explanations;
} }
/** /**
@ -216,7 +220,7 @@ public class AllocationCommands {
for (AllocationCommand command : commands.commands) { for (AllocationCommand command : commands.commands) {
builder.startObject(); builder.startObject();
builder.field(command.name()); builder.field(command.name());
AllocationCommands.lookupFactorySafe(command.name()).toXContent(command, builder, params); AllocationCommands.lookupFactorySafe(command.name()).toXContent(command, builder, params, null);
builder.endObject(); builder.endObject();
} }
builder.endArray(); builder.endArray();

View File

@ -27,7 +27,9 @@ import org.elasticsearch.cluster.routing.MutableShardRouting;
import org.elasticsearch.cluster.routing.RoutingNode; import org.elasticsearch.cluster.routing.RoutingNode;
import org.elasticsearch.cluster.routing.RoutingNodes; import org.elasticsearch.cluster.routing.RoutingNodes;
import org.elasticsearch.cluster.routing.ShardRoutingState; import org.elasticsearch.cluster.routing.ShardRoutingState;
import org.elasticsearch.cluster.routing.allocation.RerouteExplanation;
import org.elasticsearch.cluster.routing.allocation.RoutingAllocation; import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
import org.elasticsearch.cluster.routing.allocation.decider.Decision;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput; import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent; import org.elasticsearch.common.xcontent.ToXContent;
@ -105,9 +107,13 @@ public class CancelAllocationCommand implements AllocationCommand {
} }
@Override @Override
public void toXContent(CancelAllocationCommand command, XContentBuilder builder, ToXContent.Params params) throws IOException { public void toXContent(CancelAllocationCommand command, XContentBuilder builder, ToXContent.Params params, String objectName) throws IOException {
builder.startObject(); if (objectName == null) {
builder.field("index", command.shardId().index()); builder.startObject();
} else {
builder.startObject(objectName);
}
builder.field("index", command.shardId().index().name());
builder.field("shard", command.shardId().id()); builder.field("shard", command.shardId().id());
builder.field("node", command.node()); builder.field("node", command.node());
builder.field("allow_primary", command.allowPrimary()); builder.field("allow_primary", command.allowPrimary());
@ -159,7 +165,7 @@ public class CancelAllocationCommand implements AllocationCommand {
} }
@Override @Override
public void execute(RoutingAllocation allocation) throws ElasticsearchException { public RerouteExplanation execute(RoutingAllocation allocation, boolean explain) throws ElasticsearchException {
DiscoveryNode discoNode = allocation.nodes().resolveNode(node); DiscoveryNode discoNode = allocation.nodes().resolveNode(node);
boolean found = false; boolean found = false;
for (RoutingNodes.RoutingNodeIterator it = allocation.routingNodes().routingNodeIter(discoNode.id()); it.hasNext(); ) { for (RoutingNodes.RoutingNodeIterator it = allocation.routingNodes().routingNodeIter(discoNode.id()); it.hasNext(); ) {
@ -187,7 +193,12 @@ public class CancelAllocationCommand implements AllocationCommand {
// the shard is relocating to another node, cancel the recovery on the other node, and deallocate this one // the shard is relocating to another node, cancel the recovery on the other node, and deallocate this one
if (!allowPrimary && shardRouting.primary()) { if (!allowPrimary && shardRouting.primary()) {
// can't cancel a primary shard being initialized // can't cancel a primary shard being initialized
throw new ElasticsearchIllegalArgumentException("[cancel_allocation] can't cancel " + shardId + " on node " + discoNode + ", shard is primary and initializing its state"); if (explain) {
return new RerouteExplanation(this, allocation.decision(Decision.NO, "cancel_allocation_command",
"can't cancel " + shardId + " on node " + discoNode + ", shard is primary and initializing its state"));
}
throw new ElasticsearchIllegalArgumentException("[cancel_allocation] can't cancel " + shardId + " on node " +
discoNode + ", shard is primary and initializing its state");
} }
it.moveToUnassigned(); it.moveToUnassigned();
// now, go and find the shard that is initializing on the target node, and cancel it as well... // now, go and find the shard that is initializing on the target node, and cancel it as well...
@ -205,7 +216,12 @@ public class CancelAllocationCommand implements AllocationCommand {
// the shard is not relocating, its either started, or initializing, just cancel it and move on... // the shard is not relocating, its either started, or initializing, just cancel it and move on...
if (!allowPrimary && shardRouting.primary()) { if (!allowPrimary && shardRouting.primary()) {
// can't cancel a primary shard being initialized // can't cancel a primary shard being initialized
throw new ElasticsearchIllegalArgumentException("[cancel_allocation] can't cancel " + shardId + " on node " + discoNode + ", shard is primary and started"); if (explain) {
return new RerouteExplanation(this, allocation.decision(Decision.NO, "cancel_allocation_command",
"can't cancel " + shardId + " on node " + discoNode + ", shard is primary and started"));
}
throw new ElasticsearchIllegalArgumentException("[cancel_allocation] can't cancel " + shardId + " on node " +
discoNode + ", shard is primary and started");
} }
it.remove(); it.remove();
allocation.routingNodes().unassigned().add(new MutableShardRouting(shardRouting.index(), shardRouting.id(), allocation.routingNodes().unassigned().add(new MutableShardRouting(shardRouting.index(), shardRouting.id(),
@ -213,7 +229,13 @@ public class CancelAllocationCommand implements AllocationCommand {
} }
} }
if (!found) { if (!found) {
if (explain) {
return new RerouteExplanation(this, allocation.decision(Decision.NO, "cancel_allocation_command",
"can't cancel " + shardId + ", failed to find it on node " + discoNode));
}
throw new ElasticsearchIllegalArgumentException("[cancel_allocation] can't cancel " + shardId + ", failed to find it on node " + discoNode); throw new ElasticsearchIllegalArgumentException("[cancel_allocation] can't cancel " + shardId + ", failed to find it on node " + discoNode);
} }
return new RerouteExplanation(this, allocation.decision(Decision.YES, "cancel_allocation_command",
"shard " + shardId + " on node " + discoNode + " can be cancelled"));
} }
} }

View File

@ -26,6 +26,7 @@ import org.elasticsearch.cluster.node.DiscoveryNode;
import org.elasticsearch.cluster.routing.MutableShardRouting; import org.elasticsearch.cluster.routing.MutableShardRouting;
import org.elasticsearch.cluster.routing.RoutingNode; import org.elasticsearch.cluster.routing.RoutingNode;
import org.elasticsearch.cluster.routing.ShardRoutingState; import org.elasticsearch.cluster.routing.ShardRoutingState;
import org.elasticsearch.cluster.routing.allocation.RerouteExplanation;
import org.elasticsearch.cluster.routing.allocation.RoutingAllocation; import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
import org.elasticsearch.cluster.routing.allocation.decider.Decision; import org.elasticsearch.cluster.routing.allocation.decider.Decision;
import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamInput;
@ -104,9 +105,13 @@ public class MoveAllocationCommand implements AllocationCommand {
} }
@Override @Override
public void toXContent(MoveAllocationCommand command, XContentBuilder builder, ToXContent.Params params) throws IOException { public void toXContent(MoveAllocationCommand command, XContentBuilder builder, ToXContent.Params params, String objectName) throws IOException {
builder.startObject(); if (objectName == null) {
builder.field("index", command.shardId().index()); builder.startObject();
} else {
builder.startObject(objectName);
}
builder.field("index", command.shardId().index().name());
builder.field("shard", command.shardId().id()); builder.field("shard", command.shardId().id());
builder.field("from_node", command.fromNode()); builder.field("from_node", command.fromNode());
builder.field("to_node", command.toNode()); builder.field("to_node", command.toNode());
@ -142,9 +147,10 @@ public class MoveAllocationCommand implements AllocationCommand {
} }
@Override @Override
public void execute(RoutingAllocation allocation) throws ElasticsearchException { public RerouteExplanation execute(RoutingAllocation allocation, boolean explain) throws ElasticsearchException {
DiscoveryNode fromDiscoNode = allocation.nodes().resolveNode(fromNode); DiscoveryNode fromDiscoNode = allocation.nodes().resolveNode(fromNode);
DiscoveryNode toDiscoNode = allocation.nodes().resolveNode(toNode); DiscoveryNode toDiscoNode = allocation.nodes().resolveNode(toNode);
Decision decision = null;
boolean found = false; boolean found = false;
for (MutableShardRouting shardRouting : allocation.routingNodes().node(fromDiscoNode.id())) { for (MutableShardRouting shardRouting : allocation.routingNodes().node(fromDiscoNode.id())) {
@ -155,12 +161,20 @@ public class MoveAllocationCommand implements AllocationCommand {
// TODO we can possibly support also relocating cases, where we cancel relocation and move... // TODO we can possibly support also relocating cases, where we cancel relocation and move...
if (!shardRouting.started()) { if (!shardRouting.started()) {
throw new ElasticsearchIllegalArgumentException("[move_allocation] can't move " + shardId + ", shard is not started (state = " + shardRouting.state() + "]"); if (explain) {
return new RerouteExplanation(this, allocation.decision(Decision.NO, "move_allocation_command",
"shard " + shardId + " has not been started"));
}
throw new ElasticsearchIllegalArgumentException("[move_allocation] can't move " + shardId +
", shard is not started (state = " + shardRouting.state() + "]");
} }
RoutingNode toRoutingNode = allocation.routingNodes().node(toDiscoNode.id()); RoutingNode toRoutingNode = allocation.routingNodes().node(toDiscoNode.id());
Decision decision = allocation.deciders().canAllocate(shardRouting, toRoutingNode, allocation); decision = allocation.deciders().canAllocate(shardRouting, toRoutingNode, allocation);
if (decision.type() == Decision.Type.NO) { if (decision.type() == Decision.Type.NO) {
if (explain) {
return new RerouteExplanation(this, decision);
}
throw new ElasticsearchIllegalArgumentException("[move_allocation] can't move " + shardId + ", from " + fromDiscoNode + ", to " + toDiscoNode + ", since its not allowed, reason: " + decision); throw new ElasticsearchIllegalArgumentException("[move_allocation] can't move " + shardId + ", from " + fromDiscoNode + ", to " + toDiscoNode + ", since its not allowed, reason: " + decision);
} }
if (decision.type() == Decision.Type.THROTTLE) { if (decision.type() == Decision.Type.THROTTLE) {
@ -175,7 +189,12 @@ public class MoveAllocationCommand implements AllocationCommand {
} }
if (!found) { if (!found) {
if (explain) {
return new RerouteExplanation(this, allocation.decision(Decision.NO,
"move_allocation_command", "shard " + shardId + " not found"));
}
throw new ElasticsearchIllegalArgumentException("[move_allocation] can't move " + shardId + ", failed to find it on node " + fromDiscoNode); throw new ElasticsearchIllegalArgumentException("[move_allocation] can't move " + shardId + ", failed to find it on node " + fromDiscoNode);
} }
return new RerouteExplanation(this, decision);
} }
} }

View File

@ -82,6 +82,8 @@ import java.util.Map;
*/ */
public class AwarenessAllocationDecider extends AllocationDecider { public class AwarenessAllocationDecider extends AllocationDecider {
public static final String NAME = "awareness";
public static final String CLUSTER_ROUTING_ALLOCATION_AWARENESS_ATTRIBUTES = "cluster.routing.allocation.awareness.attributes"; public static final String CLUSTER_ROUTING_ALLOCATION_AWARENESS_ATTRIBUTES = "cluster.routing.allocation.awareness.attributes";
public static final String CLUSTER_ROUTING_ALLOCATION_AWARENESS_FORCE_GROUP = "cluster.routing.allocation.awareness.force."; public static final String CLUSTER_ROUTING_ALLOCATION_AWARENESS_FORCE_GROUP = "cluster.routing.allocation.awareness.force.";
@ -168,7 +170,7 @@ public class AwarenessAllocationDecider extends AllocationDecider {
private Decision underCapacity(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation, boolean moveToNode) { private Decision underCapacity(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation, boolean moveToNode) {
if (awarenessAttributes.length == 0) { if (awarenessAttributes.length == 0) {
return allocation.decision(Decision.YES, "no allocation awareness enabled"); return allocation.decision(Decision.YES, NAME, "no allocation awareness enabled");
} }
IndexMetaData indexMetaData = allocation.metaData().index(shardRouting.index()); IndexMetaData indexMetaData = allocation.metaData().index(shardRouting.index());
@ -176,7 +178,7 @@ public class AwarenessAllocationDecider extends AllocationDecider {
for (String awarenessAttribute : awarenessAttributes) { for (String awarenessAttribute : awarenessAttributes) {
// the node the shard exists on must be associated with an awareness attribute // the node the shard exists on must be associated with an awareness attribute
if (!node.node().attributes().containsKey(awarenessAttribute)) { if (!node.node().attributes().containsKey(awarenessAttribute)) {
return allocation.decision(Decision.NO, "node does not contain awareness attribute: [%s]", awarenessAttribute); return allocation.decision(Decision.NO, NAME, "node does not contain awareness attribute: [%s]", awarenessAttribute);
} }
// build attr_value -> nodes map // build attr_value -> nodes map
@ -234,7 +236,7 @@ public class AwarenessAllocationDecider extends AllocationDecider {
int currentNodeCount = shardPerAttribute.get(node.node().attributes().get(awarenessAttribute)); int currentNodeCount = shardPerAttribute.get(node.node().attributes().get(awarenessAttribute));
// if we are above with leftover, then we know we are not good, even with mod // if we are above with leftover, then we know we are not good, even with mod
if (currentNodeCount > (requiredCountPerAttribute + leftoverPerAttribute)) { if (currentNodeCount > (requiredCountPerAttribute + leftoverPerAttribute)) {
return allocation.decision(Decision.NO, "too many shards on nodes for attribute: [%s]", awarenessAttribute); return allocation.decision(Decision.NO, NAME, "too many shards on nodes for attribute: [%s]", awarenessAttribute);
} }
// all is well, we are below or same as average // all is well, we are below or same as average
if (currentNodeCount <= requiredCountPerAttribute) { if (currentNodeCount <= requiredCountPerAttribute) {
@ -242,6 +244,6 @@ public class AwarenessAllocationDecider extends AllocationDecider {
} }
} }
return allocation.decision(Decision.YES, "node meets awareness requirements"); return allocation.decision(Decision.YES, NAME, "node meets awareness requirements");
} }
} }

View File

@ -45,6 +45,8 @@ import java.util.Locale;
*/ */
public class ClusterRebalanceAllocationDecider extends AllocationDecider { public class ClusterRebalanceAllocationDecider extends AllocationDecider {
public static final String NAME = "cluster_rebalance";
/** /**
* An enum representation for the configured re-balance type. * An enum representation for the configured re-balance type.
*/ */
@ -87,27 +89,27 @@ public class ClusterRebalanceAllocationDecider extends AllocationDecider {
if (type == ClusterRebalanceType.INDICES_PRIMARIES_ACTIVE) { if (type == ClusterRebalanceType.INDICES_PRIMARIES_ACTIVE) {
// check if there are unassigned primaries. // check if there are unassigned primaries.
if ( allocation.routingNodes().hasUnassignedPrimaries() ) { if ( allocation.routingNodes().hasUnassignedPrimaries() ) {
return allocation.decision(Decision.NO, "cluster has unassigned primary shards"); return allocation.decision(Decision.NO, NAME, "cluster has unassigned primary shards");
} }
// check if there are initializing primaries that don't have a relocatingNodeId entry. // check if there are initializing primaries that don't have a relocatingNodeId entry.
if ( allocation.routingNodes().hasInactivePrimaries() ) { if ( allocation.routingNodes().hasInactivePrimaries() ) {
return allocation.decision(Decision.NO, "cluster has inactive primary shards"); return allocation.decision(Decision.NO, NAME, "cluster has inactive primary shards");
} }
return allocation.decision(Decision.YES, "all primary shards are active"); return allocation.decision(Decision.YES, NAME, "all primary shards are active");
} }
if (type == ClusterRebalanceType.INDICES_ALL_ACTIVE) { if (type == ClusterRebalanceType.INDICES_ALL_ACTIVE) {
// check if there are unassigned shards. // check if there are unassigned shards.
if ( allocation.routingNodes().hasUnassignedShards() ) { if ( allocation.routingNodes().hasUnassignedShards() ) {
return allocation.decision(Decision.NO, "cluster has unassigned shards"); return allocation.decision(Decision.NO, NAME, "cluster has unassigned shards");
} }
// in case all indices are assigned, are there initializing shards which // in case all indices are assigned, are there initializing shards which
// are not relocating? // are not relocating?
if ( allocation.routingNodes().hasInactiveShards() ) { if ( allocation.routingNodes().hasInactiveShards() ) {
return allocation.decision(Decision.NO, "cluster has inactive shards"); return allocation.decision(Decision.NO, NAME, "cluster has inactive shards");
} }
} }
// type == Type.ALWAYS // type == Type.ALWAYS
return allocation.decision(Decision.YES, "all shards are active"); return allocation.decision(Decision.YES, NAME, "all shards are active");
} }
} }

View File

@ -39,6 +39,8 @@ import org.elasticsearch.node.settings.NodeSettingsService;
*/ */
public class ConcurrentRebalanceAllocationDecider extends AllocationDecider { public class ConcurrentRebalanceAllocationDecider extends AllocationDecider {
public static final String NAME = "concurrent_rebalance";
public static final String CLUSTER_ROUTING_ALLOCATION_CLUSTER_CONCURRENT_REBALANCE = "cluster.routing.allocation.cluster_concurrent_rebalance"; public static final String CLUSTER_ROUTING_ALLOCATION_CLUSTER_CONCURRENT_REBALANCE = "cluster.routing.allocation.cluster_concurrent_rebalance";
class ApplySettings implements NodeSettingsService.Listener { class ApplySettings implements NodeSettingsService.Listener {
@ -65,12 +67,12 @@ public class ConcurrentRebalanceAllocationDecider extends AllocationDecider {
@Override @Override
public Decision canRebalance(ShardRouting shardRouting, RoutingAllocation allocation) { public Decision canRebalance(ShardRouting shardRouting, RoutingAllocation allocation) {
if (clusterConcurrentRebalance == -1) { if (clusterConcurrentRebalance == -1) {
return allocation.decision(Decision.YES, "all concurrent rebalances are allowed"); return allocation.decision(Decision.YES, NAME, "all concurrent rebalances are allowed");
} }
if (allocation.routingNodes().getRelocatingShardCount() >= clusterConcurrentRebalance) { if (allocation.routingNodes().getRelocatingShardCount() >= clusterConcurrentRebalance) {
return allocation.decision(Decision.NO, "too man concurrent rebalances [%d], limit: [%d]", return allocation.decision(Decision.NO, NAME, "too man concurrent rebalances [%d], limit: [%d]",
allocation.routingNodes().getRelocatingShardCount(), clusterConcurrentRebalance); allocation.routingNodes().getRelocatingShardCount(), clusterConcurrentRebalance);
} }
return allocation.decision(Decision.YES, "below threshold [%d] for concurrent rebalances", clusterConcurrentRebalance); return allocation.decision(Decision.YES, NAME, "below threshold [%d] for concurrent rebalances", clusterConcurrentRebalance);
} }
} }

View File

@ -20,7 +20,13 @@
package org.elasticsearch.cluster.routing.allocation.decider; package org.elasticsearch.cluster.routing.allocation.decider;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -30,7 +36,7 @@ import java.util.Locale;
* *
* @see AllocationDecider * @see AllocationDecider
*/ */
public abstract class Decision { public abstract class Decision implements ToXContent {
public static final Decision ALWAYS = new Single(Type.YES); public static final Decision ALWAYS = new Single(Type.YES);
public static final Decision YES = new Single(Type.YES); public static final Decision YES = new Single(Type.YES);
@ -40,12 +46,52 @@ public abstract class Decision {
/** /**
* Creates a simple decision * Creates a simple decision
* @param type {@link Type} of the decision * @param type {@link Type} of the decision
* @param label label for the Decider that produced this decision
* @param explanation explanation of the decision * @param explanation explanation of the decision
* @param explanationParams additional parameters for the decision * @param explanationParams additional parameters for the decision
* @return new {@link Decision} instance * @return new {@link Decision} instance
*/ */
public static Decision single(Type type, String explanation, Object... explanationParams) { public static Decision single(Type type, String label, String explanation, Object... explanationParams) {
return new Single(type, explanation, explanationParams); return new Single(type, label, explanation, explanationParams);
}
public static void writeTo(Decision decision, StreamOutput out) throws IOException {
if (decision instanceof Multi) {
// Flag specifying whether it is a Multi or Single Decision
out.writeBoolean(true);
out.writeVInt(((Multi) decision).decisions.size());
for (Decision d : ((Multi) decision).decisions) {
writeTo(d, out);
}
} else {
// Flag specifying whether it is a Multi or Single Decision
out.writeBoolean(false);
Single d = ((Single) decision);
Type.writeTo(d.type, out);
out.writeOptionalString(d.label);
// Flatten explanation on serialization, so that explanationParams
// do not need to be serialized
out.writeOptionalString(d.getExplanation());
}
}
public static Decision readFrom(StreamInput in) throws IOException {
// Determine whether to read a Single or Multi Decision
if (in.readBoolean()) {
Multi result = new Multi();
int decisionCount = in.readVInt();
for (int i = 0; i < decisionCount; i++) {
Decision s = readFrom(in);
result.decisions.add(s);
}
return result;
} else {
Single result = new Single();
result.type = Type.readFrom(in);
result.label = in.readOptionalString();
result.explanationString = in.readOptionalString();
return result;
}
} }
/** /**
@ -55,7 +101,40 @@ public abstract class Decision {
public static enum Type { public static enum Type {
YES, YES,
NO, NO,
THROTTLE THROTTLE;
public static Type resolve(String s) {
return Type.valueOf(s.toUpperCase(Locale.ROOT));
}
public static Type readFrom(StreamInput in) throws IOException {
int i = in.readVInt();
switch (i) {
case 0:
return NO;
case 1:
return YES;
case 2:
return THROTTLE;
default:
throw new ElasticsearchIllegalArgumentException("No Type for integer [" + i + "]");
}
}
public static void writeTo(Type type, StreamOutput out) throws IOException {
switch (type) {
case NO:
out.writeVInt(0);
break;
case YES:
out.writeVInt(1);
break;
case THROTTLE:
out.writeVInt(2);
default:
throw new ElasticsearchIllegalArgumentException("Invalid Type [" + type + "]");
}
}
} }
/** /**
@ -64,20 +143,28 @@ public abstract class Decision {
*/ */
public abstract Type type(); public abstract Type type();
public abstract String label();
/** /**
* Simple class representing a single decision * Simple class representing a single decision
*/ */
public static class Single extends Decision { public static class Single extends Decision {
private final Type type; private Type type;
private final String explanation; private String label;
private final Object[] explanationParams; private String explanation;
private String explanationString;
private Object[] explanationParams;
public Single() {
}
/** /**
* Creates a new {@link Single} decision of a given type * Creates a new {@link Single} decision of a given type
* @param type {@link Type} of the decision * @param type {@link Type} of the decision
*/ */
public Single(Type type) { public Single(Type type) {
this(type, null, (Object[]) null); this(type, null, null, (Object[]) null);
} }
/** /**
@ -87,8 +174,9 @@ public abstract class Decision {
* @param explanation An explanation of this {@link Decision} * @param explanation An explanation of this {@link Decision}
* @param explanationParams A set of additional parameters * @param explanationParams A set of additional parameters
*/ */
public Single(Type type, String explanation, Object... explanationParams) { public Single(Type type, String label, String explanation, Object... explanationParams) {
this.type = type; this.type = type;
this.label = label;
this.explanation = explanation; this.explanation = explanation;
this.explanationParams = explanationParams; this.explanationParams = explanationParams;
} }
@ -98,12 +186,38 @@ public abstract class Decision {
return this.type; return this.type;
} }
@Override
public String label() {
return this.label;
}
/**
* Returns the explanation string, fully formatted. Only formats the string once
*/
public String getExplanation() {
if (explanationString == null && explanation != null) {
explanationString = String.format(Locale.ROOT, explanation, explanationParams);
}
return this.explanationString;
}
@Override @Override
public String toString() { public String toString() {
if (explanation == null) { if (explanation == null) {
return type + "()"; return type + "()";
} }
return type + "(" + String.format(Locale.ROOT, explanation, explanationParams) + ")"; return type + "(" + getExplanation() + ")";
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("decider", label);
builder.field("decision", type);
String explanation = getExplanation();
builder.field("explanation", explanation != null ? explanation : "none");
builder.endObject();
return builder;
} }
} }
@ -115,7 +229,7 @@ public abstract class Decision {
private final List<Decision> decisions = Lists.newArrayList(); private final List<Decision> decisions = Lists.newArrayList();
/** /**
* Add a decission to this {@link Multi}decision instance * Add a decision to this {@link Multi}decision instance
* @param decision {@link Decision} to add * @param decision {@link Decision} to add
* @return {@link Multi}decision instance with the given decision added * @return {@link Multi}decision instance with the given decision added
*/ */
@ -138,6 +252,12 @@ public abstract class Decision {
return ret; return ret;
} }
@Override
public String label() {
// Multi decisions have no labels
return null;
}
@Override @Override
public String toString() { public String toString() {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
@ -146,5 +266,15 @@ public abstract class Decision {
} }
return sb.toString(); return sb.toString();
} }
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startArray("decisions");
for (Decision d : decisions) {
d.toXContent(builder, params);
}
builder.endArray();
return builder;
}
} }
} }

View File

@ -56,6 +56,8 @@ import org.elasticsearch.node.settings.NodeSettingsService;
@Deprecated @Deprecated
public class DisableAllocationDecider extends AllocationDecider { public class DisableAllocationDecider extends AllocationDecider {
public static final String NAME = "disable";
public static final String CLUSTER_ROUTING_ALLOCATION_DISABLE_NEW_ALLOCATION = "cluster.routing.allocation.disable_new_allocation"; public static final String CLUSTER_ROUTING_ALLOCATION_DISABLE_NEW_ALLOCATION = "cluster.routing.allocation.disable_new_allocation";
public static final String CLUSTER_ROUTING_ALLOCATION_DISABLE_ALLOCATION = "cluster.routing.allocation.disable_allocation"; public static final String CLUSTER_ROUTING_ALLOCATION_DISABLE_ALLOCATION = "cluster.routing.allocation.disable_allocation";
public static final String CLUSTER_ROUTING_ALLOCATION_DISABLE_REPLICA_ALLOCATION = "cluster.routing.allocation.disable_replica_allocation"; public static final String CLUSTER_ROUTING_ALLOCATION_DISABLE_REPLICA_ALLOCATION = "cluster.routing.allocation.disable_replica_allocation";
@ -104,28 +106,28 @@ public class DisableAllocationDecider extends AllocationDecider {
@Override @Override
public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) { public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) {
if (allocation.ignoreDisable()) { if (allocation.ignoreDisable()) {
return allocation.decision(Decision.YES, "allocation disabling is ignored"); return allocation.decision(Decision.YES, NAME, "allocation disabling is ignored");
} }
Settings indexSettings = allocation.routingNodes().metaData().index(shardRouting.index()).settings(); Settings indexSettings = allocation.routingNodes().metaData().index(shardRouting.index()).settings();
if (shardRouting.primary() && !allocation.routingNodes().routingTable().index(shardRouting.index()).shard(shardRouting.id()).primaryAllocatedPostApi()) { if (shardRouting.primary() && !allocation.routingNodes().routingTable().index(shardRouting.index()).shard(shardRouting.id()).primaryAllocatedPostApi()) {
// if its primary, and it hasn't been allocated post API (meaning its a "fresh newly created shard"), only disable allocation // if its primary, and it hasn't been allocated post API (meaning its a "fresh newly created shard"), only disable allocation
// on a special disable allocation flag // on a special disable allocation flag
if (indexSettings.getAsBoolean(INDEX_ROUTING_ALLOCATION_DISABLE_NEW_ALLOCATION, disableNewAllocation)) { if (indexSettings.getAsBoolean(INDEX_ROUTING_ALLOCATION_DISABLE_NEW_ALLOCATION, disableNewAllocation)) {
return allocation.decision(Decision.NO, "new primary allocation is disabled"); return allocation.decision(Decision.NO, NAME, "new primary allocation is disabled");
} else { } else {
return allocation.decision(Decision.YES, "new primary allocation is enabled"); return allocation.decision(Decision.YES, NAME, "new primary allocation is enabled");
} }
} }
if (indexSettings.getAsBoolean(INDEX_ROUTING_ALLOCATION_DISABLE_ALLOCATION, disableAllocation)) { if (indexSettings.getAsBoolean(INDEX_ROUTING_ALLOCATION_DISABLE_ALLOCATION, disableAllocation)) {
return allocation.decision(Decision.NO, "all allocation is disabled"); return allocation.decision(Decision.NO, NAME, "all allocation is disabled");
} }
if (indexSettings.getAsBoolean(INDEX_ROUTING_ALLOCATION_DISABLE_REPLICA_ALLOCATION, disableReplicaAllocation)) { if (indexSettings.getAsBoolean(INDEX_ROUTING_ALLOCATION_DISABLE_REPLICA_ALLOCATION, disableReplicaAllocation)) {
if (shardRouting.primary()) { if (shardRouting.primary()) {
return allocation.decision(Decision.YES, "primary allocation is enabled"); return allocation.decision(Decision.YES, NAME, "primary allocation is enabled");
} else { } else {
return allocation.decision(Decision.NO, "replica allocation is disabled"); return allocation.decision(Decision.NO, NAME, "replica allocation is disabled");
} }
} }
return allocation.decision(Decision.YES, "all allocation is enabled"); return allocation.decision(Decision.YES, NAME, "all allocation is enabled");
} }
} }

View File

@ -59,6 +59,8 @@ import static org.elasticsearch.cluster.InternalClusterInfoService.shardIdentifi
*/ */
public class DiskThresholdDecider extends AllocationDecider { public class DiskThresholdDecider extends AllocationDecider {
public static final String NAME = "disk_threshold";
private volatile Double freeDiskThresholdLow; private volatile Double freeDiskThresholdLow;
private volatile Double freeDiskThresholdHigh; private volatile Double freeDiskThresholdHigh;
private volatile ByteSizeValue freeBytesThresholdLow; private volatile ByteSizeValue freeBytesThresholdLow;
@ -129,11 +131,11 @@ public class DiskThresholdDecider extends AllocationDecider {
public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) { public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) {
if (!enabled) { if (!enabled) {
return allocation.decision(Decision.YES, "disk threshold decider disabled"); return allocation.decision(Decision.YES, NAME, "disk threshold decider disabled");
} }
// Allow allocation regardless if only a single node is available // Allow allocation regardless if only a single node is available
if (allocation.nodes().size() <= 1) { if (allocation.nodes().size() <= 1) {
return allocation.decision(Decision.YES, "only a single node is present"); return allocation.decision(Decision.YES, NAME, "only a single node is present");
} }
ClusterInfo clusterInfo = allocation.clusterInfo(); ClusterInfo clusterInfo = allocation.clusterInfo();
@ -141,7 +143,7 @@ public class DiskThresholdDecider extends AllocationDecider {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Cluster info unavailable for disk threshold decider, allowing allocation."); logger.trace("Cluster info unavailable for disk threshold decider, allowing allocation.");
} }
return allocation.decision(Decision.YES, "cluster info unavailable"); return allocation.decision(Decision.YES, NAME, "cluster info unavailable");
} }
Map<String, DiskUsage> usages = clusterInfo.getNodeDiskUsages(); Map<String, DiskUsage> usages = clusterInfo.getNodeDiskUsages();
@ -150,7 +152,7 @@ public class DiskThresholdDecider extends AllocationDecider {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Unable to determine disk usages for disk-aware allocation, allowing allocation"); logger.trace("Unable to determine disk usages for disk-aware allocation, allowing allocation");
} }
return allocation.decision(Decision.YES, "disk usages unavailable"); return allocation.decision(Decision.YES, NAME, "disk usages unavailable");
} }
DiskUsage usage = usages.get(node.nodeId()); DiskUsage usage = usages.get(node.nodeId());
@ -175,7 +177,7 @@ public class DiskThresholdDecider extends AllocationDecider {
logger.debug("Less than the required {} free bytes threshold ({} bytes free) on node {}, preventing allocation", logger.debug("Less than the required {} free bytes threshold ({} bytes free) on node {}, preventing allocation",
freeBytesThresholdLow, freeBytes, node.nodeId()); freeBytesThresholdLow, freeBytes, node.nodeId());
} }
return allocation.decision(Decision.NO, "less than required [%s] free on node, free: [%s]", return allocation.decision(Decision.NO, NAME, "less than required [%s] free on node, free: [%s]",
freeBytesThresholdLow, new ByteSizeValue(freeBytes)); freeBytesThresholdLow, new ByteSizeValue(freeBytes));
} }
if (freeDiskPercentage < freeDiskThresholdLow) { if (freeDiskPercentage < freeDiskThresholdLow) {
@ -183,7 +185,7 @@ public class DiskThresholdDecider extends AllocationDecider {
logger.debug("Less than the required {}% free disk threshold ({}% free) on node [{}], preventing allocation", logger.debug("Less than the required {}% free disk threshold ({}% free) on node [{}], preventing allocation",
freeDiskThresholdLow, freeDiskPercentage, node.nodeId()); freeDiskThresholdLow, freeDiskPercentage, node.nodeId());
} }
return allocation.decision(Decision.NO, "less than required [%d%%] free disk on node, free: [%d%%]", return allocation.decision(Decision.NO, NAME, "less than required [%d%%] free disk on node, free: [%d%%]",
freeDiskThresholdLow, freeDiskThresholdLow); freeDiskThresholdLow, freeDiskThresholdLow);
} }
@ -195,26 +197,26 @@ public class DiskThresholdDecider extends AllocationDecider {
if (freeBytesAfterShard < freeBytesThresholdHigh.bytes()) { if (freeBytesAfterShard < freeBytesThresholdHigh.bytes()) {
logger.warn("After allocating, node [{}] would have less than the required {} free bytes threshold ({} bytes free), preventing allocation", logger.warn("After allocating, node [{}] would have less than the required {} free bytes threshold ({} bytes free), preventing allocation",
node.nodeId(), freeBytesThresholdHigh, freeBytesAfterShard); node.nodeId(), freeBytesThresholdHigh, freeBytesAfterShard);
return allocation.decision(Decision.NO, "after allocation less than required [%s] free on node, free: [%s]", return allocation.decision(Decision.NO, NAME, "after allocation less than required [%s] free on node, free: [%s]",
freeBytesThresholdLow, new ByteSizeValue(freeBytesAfterShard)); freeBytesThresholdLow, new ByteSizeValue(freeBytesAfterShard));
} }
if (freeSpaceAfterShard < freeDiskThresholdHigh) { if (freeSpaceAfterShard < freeDiskThresholdHigh) {
logger.warn("After allocating, node [{}] would have less than the required {}% free disk threshold ({}% free), preventing allocation", logger.warn("After allocating, node [{}] would have less than the required {}% free disk threshold ({}% free), preventing allocation",
node.nodeId(), freeDiskThresholdHigh, freeSpaceAfterShard); node.nodeId(), freeDiskThresholdHigh, freeSpaceAfterShard);
return allocation.decision(Decision.NO, "after allocation less than required [%d%%] free disk on node, free: [%d%%]", return allocation.decision(Decision.NO, NAME, "after allocation less than required [%d%%] free disk on node, free: [%d%%]",
freeDiskThresholdLow, freeSpaceAfterShard); freeDiskThresholdLow, freeSpaceAfterShard);
} }
return allocation.decision(Decision.YES, "enough disk for shard on node, free: [%s]", new ByteSizeValue(freeBytes)); return allocation.decision(Decision.YES, NAME, "enough disk for shard on node, free: [%s]", new ByteSizeValue(freeBytes));
} }
public Decision canRemain(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) { public Decision canRemain(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) {
if (!enabled) { if (!enabled) {
return allocation.decision(Decision.YES, "disk threshold decider disabled"); return allocation.decision(Decision.YES, NAME, "disk threshold decider disabled");
} }
// Allow allocation regardless if only a single node is available // Allow allocation regardless if only a single node is available
if (allocation.nodes().size() <= 1) { if (allocation.nodes().size() <= 1) {
return allocation.decision(Decision.YES, "only a single node is present"); return allocation.decision(Decision.YES, NAME, "only a single node is present");
} }
ClusterInfo clusterInfo = allocation.clusterInfo(); ClusterInfo clusterInfo = allocation.clusterInfo();
@ -222,7 +224,7 @@ public class DiskThresholdDecider extends AllocationDecider {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Cluster info unavailable for disk threshold decider, allowing allocation."); logger.trace("Cluster info unavailable for disk threshold decider, allowing allocation.");
} }
return allocation.decision(Decision.YES, "cluster info unavailable"); return allocation.decision(Decision.YES, NAME, "cluster info unavailable");
} }
Map<String, DiskUsage> usages = clusterInfo.getNodeDiskUsages(); Map<String, DiskUsage> usages = clusterInfo.getNodeDiskUsages();
@ -230,7 +232,7 @@ public class DiskThresholdDecider extends AllocationDecider {
if (logger.isTraceEnabled()) { if (logger.isTraceEnabled()) {
logger.trace("Unable to determine disk usages for disk-aware allocation, allowing allocation"); logger.trace("Unable to determine disk usages for disk-aware allocation, allowing allocation");
} }
return allocation.decision(Decision.YES, "disk usages unavailable"); return allocation.decision(Decision.YES, NAME, "disk usages unavailable");
} }
DiskUsage usage = usages.get(node.nodeId()); DiskUsage usage = usages.get(node.nodeId());
@ -255,7 +257,7 @@ public class DiskThresholdDecider extends AllocationDecider {
logger.debug("Less than the required {} free bytes threshold ({} bytes free) on node {}, shard cannot remain", logger.debug("Less than the required {} free bytes threshold ({} bytes free) on node {}, shard cannot remain",
freeBytesThresholdHigh, freeBytes, node.nodeId()); freeBytesThresholdHigh, freeBytes, node.nodeId());
} }
return allocation.decision(Decision.NO, "after allocation less than required [%s] free on node, free: [%s]", return allocation.decision(Decision.NO, NAME, "after allocation less than required [%s] free on node, free: [%s]",
freeBytesThresholdHigh, new ByteSizeValue(freeBytes)); freeBytesThresholdHigh, new ByteSizeValue(freeBytes));
} }
if (freeDiskPercentage < freeDiskThresholdHigh) { if (freeDiskPercentage < freeDiskThresholdHigh) {
@ -263,11 +265,11 @@ public class DiskThresholdDecider extends AllocationDecider {
logger.debug("Less than the required {}% free disk threshold ({}% free) on node {}, shard cannot remain", logger.debug("Less than the required {}% free disk threshold ({}% free) on node {}, shard cannot remain",
freeDiskThresholdHigh, freeDiskPercentage, node.nodeId()); freeDiskThresholdHigh, freeDiskPercentage, node.nodeId());
} }
return allocation.decision(Decision.NO, "after allocation less than required [%d%%] free disk on node, free: [%d%%]", return allocation.decision(Decision.NO, NAME, "after allocation less than required [%d%%] free disk on node, free: [%d%%]",
freeDiskThresholdHigh, freeDiskPercentage); freeDiskThresholdHigh, freeDiskPercentage);
} }
return allocation.decision(Decision.YES, "enough disk for shard to remain on node, free: [%s]", new ByteSizeValue(freeBytes)); return allocation.decision(Decision.YES, NAME, "enough disk for shard to remain on node, free: [%s]", new ByteSizeValue(freeBytes));
} }
/** /**

View File

@ -45,6 +45,8 @@ import java.util.Locale;
*/ */
public class EnableAllocationDecider extends AllocationDecider implements NodeSettingsService.Listener { public class EnableAllocationDecider extends AllocationDecider implements NodeSettingsService.Listener {
public static final String NAME = "enable";
public static final String CLUSTER_ROUTING_ALLOCATION_ENABLE = "cluster.routing.allocation.enable"; public static final String CLUSTER_ROUTING_ALLOCATION_ENABLE = "cluster.routing.allocation.enable";
public static final String INDEX_ROUTING_ALLOCATION_ENABLE = "index.routing.allocation.enable"; public static final String INDEX_ROUTING_ALLOCATION_ENABLE = "index.routing.allocation.enable";
@ -60,7 +62,7 @@ public class EnableAllocationDecider extends AllocationDecider implements NodeSe
@Override @Override
public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) { public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) {
if (allocation.ignoreDisable()) { if (allocation.ignoreDisable()) {
return allocation.decision(Decision.YES, "allocation disabling is ignored"); return allocation.decision(Decision.YES, NAME, "allocation disabling is ignored");
} }
Settings indexSettings = allocation.routingNodes().metaData().index(shardRouting.index()).settings(); Settings indexSettings = allocation.routingNodes().metaData().index(shardRouting.index()).settings();
@ -73,20 +75,20 @@ public class EnableAllocationDecider extends AllocationDecider implements NodeSe
} }
switch (enable) { switch (enable) {
case ALL: case ALL:
return allocation.decision(Decision.YES, "all allocations are allowed"); return allocation.decision(Decision.YES, NAME, "all allocations are allowed");
case NONE: case NONE:
return allocation.decision(Decision.NO, "no allocations are allowed"); return allocation.decision(Decision.NO, NAME, "no allocations are allowed");
case NEW_PRIMARIES: case NEW_PRIMARIES:
if (shardRouting.primary() && !allocation.routingNodes().routingTable().index(shardRouting.index()).shard(shardRouting.id()).primaryAllocatedPostApi()) { if (shardRouting.primary() && !allocation.routingNodes().routingTable().index(shardRouting.index()).shard(shardRouting.id()).primaryAllocatedPostApi()) {
return allocation.decision(Decision.YES, "new primary allocations are allowed"); return allocation.decision(Decision.YES, NAME, "new primary allocations are allowed");
} else { } else {
return allocation.decision(Decision.NO, "non-new primary allocations are disallowed"); return allocation.decision(Decision.NO, NAME, "non-new primary allocations are disallowed");
} }
case PRIMARIES: case PRIMARIES:
if (shardRouting.primary()) { if (shardRouting.primary()) {
return allocation.decision(Decision.YES, "primary allocations are allowed"); return allocation.decision(Decision.YES, NAME, "primary allocations are allowed");
} else { } else {
return allocation.decision(Decision.NO, "replica allocations are disallowed"); return allocation.decision(Decision.NO, NAME, "replica allocations are disallowed");
} }
default: default:
throw new ElasticsearchIllegalStateException("Unknown allocation option"); throw new ElasticsearchIllegalStateException("Unknown allocation option");

View File

@ -60,6 +60,8 @@ import static org.elasticsearch.cluster.node.DiscoveryNodeFilters.OpType.OR;
*/ */
public class FilterAllocationDecider extends AllocationDecider { public class FilterAllocationDecider extends AllocationDecider {
public static final String NAME = "filter";
public static final String INDEX_ROUTING_REQUIRE_GROUP = "index.routing.allocation.require."; public static final String INDEX_ROUTING_REQUIRE_GROUP = "index.routing.allocation.require.";
public static final String INDEX_ROUTING_INCLUDE_GROUP = "index.routing.allocation.include."; public static final String INDEX_ROUTING_INCLUDE_GROUP = "index.routing.allocation.include.";
public static final String INDEX_ROUTING_EXCLUDE_GROUP = "index.routing.allocation.exclude."; public static final String INDEX_ROUTING_EXCLUDE_GROUP = "index.routing.allocation.exclude.";
@ -109,38 +111,38 @@ public class FilterAllocationDecider extends AllocationDecider {
private Decision shouldFilter(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) { private Decision shouldFilter(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) {
if (clusterRequireFilters != null) { if (clusterRequireFilters != null) {
if (!clusterRequireFilters.match(node.node())) { if (!clusterRequireFilters.match(node.node())) {
return allocation.decision(Decision.NO, "node does not match global required filters [%s]", clusterRequireFilters); return allocation.decision(Decision.NO, NAME, "node does not match global required filters [%s]", clusterRequireFilters);
} }
} }
if (clusterIncludeFilters != null) { if (clusterIncludeFilters != null) {
if (!clusterIncludeFilters.match(node.node())) { if (!clusterIncludeFilters.match(node.node())) {
return allocation.decision(Decision.NO, "node does not match global include filters [%s]", clusterIncludeFilters); return allocation.decision(Decision.NO, NAME, "node does not match global include filters [%s]", clusterIncludeFilters);
} }
} }
if (clusterExcludeFilters != null) { if (clusterExcludeFilters != null) {
if (clusterExcludeFilters.match(node.node())) { if (clusterExcludeFilters.match(node.node())) {
return allocation.decision(Decision.NO, "node matches global exclude filters [%s]", clusterExcludeFilters); return allocation.decision(Decision.NO, NAME, "node matches global exclude filters [%s]", clusterExcludeFilters);
} }
} }
IndexMetaData indexMd = allocation.routingNodes().metaData().index(shardRouting.index()); IndexMetaData indexMd = allocation.routingNodes().metaData().index(shardRouting.index());
if (indexMd.requireFilters() != null) { if (indexMd.requireFilters() != null) {
if (!indexMd.requireFilters().match(node.node())) { if (!indexMd.requireFilters().match(node.node())) {
return allocation.decision(Decision.NO, "node does not match index required filters [%s]", indexMd.requireFilters()); return allocation.decision(Decision.NO, NAME, "node does not match index required filters [%s]", indexMd.requireFilters());
} }
} }
if (indexMd.includeFilters() != null) { if (indexMd.includeFilters() != null) {
if (!indexMd.includeFilters().match(node.node())) { if (!indexMd.includeFilters().match(node.node())) {
return allocation.decision(Decision.NO, "node does not match index include filters [%s]", indexMd.includeFilters()); return allocation.decision(Decision.NO, NAME, "node does not match index include filters [%s]", indexMd.includeFilters());
} }
} }
if (indexMd.excludeFilters() != null) { if (indexMd.excludeFilters() != null) {
if (indexMd.excludeFilters().match(node.node())) { if (indexMd.excludeFilters().match(node.node())) {
return allocation.decision(Decision.NO, "node matches index exclude filters [%s]", indexMd.excludeFilters()); return allocation.decision(Decision.NO, NAME, "node matches index exclude filters [%s]", indexMd.excludeFilters());
} }
} }
return allocation.decision(Decision.YES, "node passes include/exclude/require filters"); return allocation.decision(Decision.YES, NAME, "node passes include/exclude/require filters");
} }
class ApplySettings implements NodeSettingsService.Listener { class ApplySettings implements NodeSettingsService.Listener {

View File

@ -36,6 +36,8 @@ import org.elasticsearch.common.settings.Settings;
*/ */
public class NodeVersionAllocationDecider extends AllocationDecider { public class NodeVersionAllocationDecider extends AllocationDecider {
public static final String NAME = "node_version";
@Inject @Inject
public NodeVersionAllocationDecider(Settings settings) { public NodeVersionAllocationDecider(Settings settings) {
super(settings); super(settings);
@ -49,11 +51,11 @@ public class NodeVersionAllocationDecider extends AllocationDecider {
if (sourceNodeId == null) { // we allocate - check primary if (sourceNodeId == null) { // we allocate - check primary
if (shardRouting.primary()) { if (shardRouting.primary()) {
// we are the primary we can allocate wherever // we are the primary we can allocate wherever
return allocation.decision(Decision.YES, "primary shard can be allocated anywhere"); return allocation.decision(Decision.YES, NAME, "primary shard can be allocated anywhere");
} }
final MutableShardRouting primary = allocation.routingNodes().activePrimary(shardRouting); final MutableShardRouting primary = allocation.routingNodes().activePrimary(shardRouting);
if (primary == null) { // we have a primary - it's a start ;) if (primary == null) { // we have a primary - it's a start ;)
return allocation.decision(Decision.YES, "no active primary shard yet"); return allocation.decision(Decision.YES, NAME, "no active primary shard yet");
} }
sourceNodeId = primary.currentNodeId(); sourceNodeId = primary.currentNodeId();
} }
@ -67,10 +69,10 @@ public class NodeVersionAllocationDecider extends AllocationDecider {
/* we can allocate if we can recover from a node that is younger or on the same version /* we can allocate if we can recover from a node that is younger or on the same version
* if the primary is already running on a newer version that won't work due to possible * if the primary is already running on a newer version that won't work due to possible
* differences in the lucene index format etc.*/ * differences in the lucene index format etc.*/
return allocation.decision(Decision.YES, "target node version [%s] is same or newer than source node version [%s]", return allocation.decision(Decision.YES, NAME, "target node version [%s] is same or newer than source node version [%s]",
target.node().version(), source.node().version()); target.node().version(), source.node().version());
} else { } else {
return allocation.decision(Decision.NO, "target node version [%s] is older than source node version [%s]", return allocation.decision(Decision.NO, NAME, "target node version [%s] is older than source node version [%s]",
target.node().version(), source.node().version()); target.node().version(), source.node().version());
} }
} }

View File

@ -29,6 +29,8 @@ import org.elasticsearch.common.settings.Settings;
*/ */
public class RebalanceOnlyWhenActiveAllocationDecider extends AllocationDecider { public class RebalanceOnlyWhenActiveAllocationDecider extends AllocationDecider {
public static final String NAME = "rebalance_only_when_active";
@Inject @Inject
public RebalanceOnlyWhenActiveAllocationDecider(Settings settings) { public RebalanceOnlyWhenActiveAllocationDecider(Settings settings) {
super(settings); super(settings);
@ -39,8 +41,8 @@ public class RebalanceOnlyWhenActiveAllocationDecider extends AllocationDecider
// its ok to check for active here, since in relocation, a shard is split into two in routing // its ok to check for active here, since in relocation, a shard is split into two in routing
// nodes, once relocating, and one initializing // nodes, once relocating, and one initializing
if (!allocation.routingNodes().allReplicasActive(shardRouting)) { if (!allocation.routingNodes().allReplicasActive(shardRouting)) {
return allocation.decision(Decision.NO, "not all replicas are active in cluster"); return allocation.decision(Decision.NO, NAME, "not all replicas are active in cluster");
} }
return allocation.decision(Decision.YES, "all replicas are active in cluster"); return allocation.decision(Decision.YES, NAME, "all replicas are active in cluster");
} }
} }

View File

@ -31,6 +31,8 @@ import org.elasticsearch.common.settings.Settings;
*/ */
public class ReplicaAfterPrimaryActiveAllocationDecider extends AllocationDecider { public class ReplicaAfterPrimaryActiveAllocationDecider extends AllocationDecider {
private static final String NAME = "replica_after_primary_active";
@Inject @Inject
public ReplicaAfterPrimaryActiveAllocationDecider(Settings settings) { public ReplicaAfterPrimaryActiveAllocationDecider(Settings settings) {
super(settings); super(settings);
@ -43,12 +45,12 @@ public class ReplicaAfterPrimaryActiveAllocationDecider extends AllocationDecide
public Decision canAllocate(ShardRouting shardRouting, RoutingAllocation allocation) { public Decision canAllocate(ShardRouting shardRouting, RoutingAllocation allocation) {
if (shardRouting.primary()) { if (shardRouting.primary()) {
return allocation.decision(Decision.YES, "shard is primary"); return allocation.decision(Decision.YES, NAME, "shard is primary");
} }
MutableShardRouting primary = allocation.routingNodes().activePrimary(shardRouting); MutableShardRouting primary = allocation.routingNodes().activePrimary(shardRouting);
if (primary == null) { if (primary == null) {
return allocation.decision(Decision.NO, "primary shard is not yet active"); return allocation.decision(Decision.NO, NAME, "primary shard is not yet active");
} }
return allocation.decision(Decision.YES, "primary is already active"); return allocation.decision(Decision.YES, NAME, "primary is already active");
} }
} }

View File

@ -44,6 +44,8 @@ import org.elasticsearch.common.settings.Settings;
*/ */
public class SameShardAllocationDecider extends AllocationDecider { public class SameShardAllocationDecider extends AllocationDecider {
public static final String NAME = "same_shard";
public static final String SAME_HOST_SETTING = "cluster.routing.allocation.same_shard.host"; public static final String SAME_HOST_SETTING = "cluster.routing.allocation.same_shard.host";
private final boolean sameHost; private final boolean sameHost;
@ -60,7 +62,7 @@ public class SameShardAllocationDecider extends AllocationDecider {
Iterable<MutableShardRouting> assignedShards = allocation.routingNodes().assignedShards(shardRouting); Iterable<MutableShardRouting> assignedShards = allocation.routingNodes().assignedShards(shardRouting);
for (MutableShardRouting assignedShard : assignedShards) { for (MutableShardRouting assignedShard : assignedShards) {
if (node.nodeId().equals(assignedShard.currentNodeId())) { if (node.nodeId().equals(assignedShard.currentNodeId())) {
return allocation.decision(Decision.NO, "shard cannot be allocated on same node [%s] it already exists on", node.nodeId()); return allocation.decision(Decision.NO, NAME, "shard cannot be allocated on same node [%s] it already exists on", node.nodeId());
} }
} }
if (sameHost) { if (sameHost) {
@ -83,14 +85,14 @@ public class SameShardAllocationDecider extends AllocationDecider {
if (checkNodeOnSameHost) { if (checkNodeOnSameHost) {
for (MutableShardRouting assignedShard : assignedShards) { for (MutableShardRouting assignedShard : assignedShards) {
if (checkNode.nodeId().equals(assignedShard.currentNodeId())) { if (checkNode.nodeId().equals(assignedShard.currentNodeId())) {
return allocation.decision(Decision.NO, "shard cannot be allocated on same host [%s] it already exists on", return allocation.decision(Decision.NO, NAME,
node.nodeId()); "shard cannot be allocated on same host [%s] it already exists on", node.nodeId());
} }
} }
} }
} }
} }
} }
return allocation.decision(Decision.YES, "shard is not allocated to same node or host"); return allocation.decision(Decision.YES, NAME, "shard is not allocated to same node or host");
} }
} }

View File

@ -49,6 +49,8 @@ import org.elasticsearch.common.settings.Settings;
*/ */
public class ShardsLimitAllocationDecider extends AllocationDecider { public class ShardsLimitAllocationDecider extends AllocationDecider {
public static final String NAME = "shards_limit";
/** /**
* Controls the maximum number of shards per index on a single Elasticsearch * Controls the maximum number of shards per index on a single Elasticsearch
* node. Negative values are interpreted as unlimited. * node. Negative values are interpreted as unlimited.
@ -65,7 +67,7 @@ public class ShardsLimitAllocationDecider extends AllocationDecider {
IndexMetaData indexMd = allocation.routingNodes().metaData().index(shardRouting.index()); IndexMetaData indexMd = allocation.routingNodes().metaData().index(shardRouting.index());
int totalShardsPerNode = indexMd.settings().getAsInt(INDEX_TOTAL_SHARDS_PER_NODE, -1); int totalShardsPerNode = indexMd.settings().getAsInt(INDEX_TOTAL_SHARDS_PER_NODE, -1);
if (totalShardsPerNode <= 0) { if (totalShardsPerNode <= 0) {
return allocation.decision(Decision.YES, "total shard limit disabled: [%d] <= 0", totalShardsPerNode); return allocation.decision(Decision.YES, NAME, "total shard limit disabled: [%d] <= 0", totalShardsPerNode);
} }
int nodeCount = 0; int nodeCount = 0;
@ -80,10 +82,10 @@ public class ShardsLimitAllocationDecider extends AllocationDecider {
nodeCount++; nodeCount++;
} }
if (nodeCount >= totalShardsPerNode) { if (nodeCount >= totalShardsPerNode) {
return allocation.decision(Decision.NO, "too many shards for this index on node [%d], limit: [%d]", return allocation.decision(Decision.NO, NAME, "too many shards for this index on node [%d], limit: [%d]",
nodeCount, totalShardsPerNode); nodeCount, totalShardsPerNode);
} }
return allocation.decision(Decision.YES, "shard count under limit [%d] of total shards per node", totalShardsPerNode); return allocation.decision(Decision.YES, NAME, "shard count under limit [%d] of total shards per node", totalShardsPerNode);
} }
@Override @Override
@ -91,7 +93,7 @@ public class ShardsLimitAllocationDecider extends AllocationDecider {
IndexMetaData indexMd = allocation.routingNodes().metaData().index(shardRouting.index()); IndexMetaData indexMd = allocation.routingNodes().metaData().index(shardRouting.index());
int totalShardsPerNode = indexMd.settings().getAsInt(INDEX_TOTAL_SHARDS_PER_NODE, -1); int totalShardsPerNode = indexMd.settings().getAsInt(INDEX_TOTAL_SHARDS_PER_NODE, -1);
if (totalShardsPerNode <= 0) { if (totalShardsPerNode <= 0) {
return allocation.decision(Decision.YES, "total shard limit disabled: [%d] <= 0", totalShardsPerNode); return allocation.decision(Decision.YES, NAME, "total shard limit disabled: [%d] <= 0", totalShardsPerNode);
} }
int nodeCount = 0; int nodeCount = 0;
@ -106,9 +108,9 @@ public class ShardsLimitAllocationDecider extends AllocationDecider {
nodeCount++; nodeCount++;
} }
if (nodeCount > totalShardsPerNode) { if (nodeCount > totalShardsPerNode) {
return allocation.decision(Decision.NO, "too many shards for this index on node [%d], limit: [%d]", return allocation.decision(Decision.NO, NAME, "too many shards for this index on node [%d], limit: [%d]",
nodeCount, totalShardsPerNode); nodeCount, totalShardsPerNode);
} }
return allocation.decision(Decision.YES, "shard count under limit [%d] of total shards per node", totalShardsPerNode); return allocation.decision(Decision.YES, NAME, "shard count under limit [%d] of total shards per node", totalShardsPerNode);
} }
} }

View File

@ -34,6 +34,8 @@ import org.elasticsearch.node.settings.NodeSettingsService;
*/ */
public class SnapshotInProgressAllocationDecider extends AllocationDecider { public class SnapshotInProgressAllocationDecider extends AllocationDecider {
public static final String NAME = "snapshot_in_progress";
/** /**
* Disables relocation of shards that are currently being snapshotted. * Disables relocation of shards that are currently being snapshotted.
*/ */
@ -99,19 +101,19 @@ public class SnapshotInProgressAllocationDecider extends AllocationDecider {
SnapshotMetaData snapshotMetaData = allocation.metaData().custom(SnapshotMetaData.TYPE); SnapshotMetaData snapshotMetaData = allocation.metaData().custom(SnapshotMetaData.TYPE);
if (snapshotMetaData == null) { if (snapshotMetaData == null) {
// Snapshots are not running // Snapshots are not running
return allocation.decision(Decision.YES, "no snapshots are currently running"); return allocation.decision(Decision.YES, NAME, "no snapshots are currently running");
} }
for (SnapshotMetaData.Entry snapshot : snapshotMetaData.entries()) { for (SnapshotMetaData.Entry snapshot : snapshotMetaData.entries()) {
SnapshotMetaData.ShardSnapshotStatus shardSnapshotStatus = snapshot.shards().get(shardRouting.shardId()); SnapshotMetaData.ShardSnapshotStatus shardSnapshotStatus = snapshot.shards().get(shardRouting.shardId());
if (shardSnapshotStatus != null && !shardSnapshotStatus.state().completed() && shardSnapshotStatus.nodeId() != null && shardSnapshotStatus.nodeId().equals(shardRouting.currentNodeId())) { if (shardSnapshotStatus != null && !shardSnapshotStatus.state().completed() && shardSnapshotStatus.nodeId() != null && shardSnapshotStatus.nodeId().equals(shardRouting.currentNodeId())) {
logger.trace("Preventing snapshotted shard [{}] to be moved from node [{}]", shardRouting.shardId(), shardSnapshotStatus.nodeId()); logger.trace("Preventing snapshotted shard [{}] to be moved from node [{}]", shardRouting.shardId(), shardSnapshotStatus.nodeId());
return allocation.decision(Decision.NO, "snapshot for shard [%s] is currently running on node [%s]", return allocation.decision(Decision.NO, NAME, "snapshot for shard [%s] is currently running on node [%s]",
shardRouting.shardId(), shardSnapshotStatus.nodeId()); shardRouting.shardId(), shardSnapshotStatus.nodeId());
} }
} }
} }
return allocation.decision(Decision.YES, "shard not primary or relocation disabled"); return allocation.decision(Decision.YES, NAME, "shard not primary or relocation disabled");
} }
} }

View File

@ -50,6 +50,8 @@ import org.elasticsearch.node.settings.NodeSettingsService;
*/ */
public class ThrottlingAllocationDecider extends AllocationDecider { public class ThrottlingAllocationDecider extends AllocationDecider {
public static final String NAME = "throttling";
public static final String CLUSTER_ROUTING_ALLOCATION_NODE_INITIAL_PRIMARIES_RECOVERIES = "cluster.routing.allocation.node_initial_primaries_recoveries"; public static final String CLUSTER_ROUTING_ALLOCATION_NODE_INITIAL_PRIMARIES_RECOVERIES = "cluster.routing.allocation.node_initial_primaries_recoveries";
public static final String CLUSTER_ROUTING_ALLOCATION_NODE_CONCURRENT_RECOVERIES = "cluster.routing.allocation.node_concurrent_recoveries"; public static final String CLUSTER_ROUTING_ALLOCATION_NODE_CONCURRENT_RECOVERIES = "cluster.routing.allocation.node_concurrent_recoveries";
public static final int DEFAULT_CLUSTER_ROUTING_ALLOCATION_NODE_CONCURRENT_RECOVERIES = 2; public static final int DEFAULT_CLUSTER_ROUTING_ALLOCATION_NODE_CONCURRENT_RECOVERIES = 2;
@ -85,10 +87,10 @@ public class ThrottlingAllocationDecider extends AllocationDecider {
} }
} }
if (primariesInRecovery >= primariesInitialRecoveries) { if (primariesInRecovery >= primariesInitialRecoveries) {
return allocation.decision(Decision.THROTTLE, "too many primaries currently recovering [%d], limit: [%d]", return allocation.decision(Decision.THROTTLE, NAME, "too many primaries currently recovering [%d], limit: [%d]",
primariesInRecovery, primariesInitialRecoveries); primariesInRecovery, primariesInitialRecoveries);
} else { } else {
return allocation.decision(Decision.YES, "below primary recovery limit of [%d]", primariesInitialRecoveries); return allocation.decision(Decision.YES, NAME, "below primary recovery limit of [%d]", primariesInitialRecoveries);
} }
} }
} }
@ -107,10 +109,10 @@ public class ThrottlingAllocationDecider extends AllocationDecider {
} }
} }
if (currentRecoveries >= concurrentRecoveries) { if (currentRecoveries >= concurrentRecoveries) {
return allocation.decision(Decision.THROTTLE, "too many shards currently recovering [%d], limit: [%d]", return allocation.decision(Decision.THROTTLE, NAME, "too many shards currently recovering [%d], limit: [%d]",
currentRecoveries, concurrentRecoveries); currentRecoveries, concurrentRecoveries);
} else { } else {
return allocation.decision(Decision.YES, "below shard recovery limit of [%d]", concurrentRecoveries); return allocation.decision(Decision.YES, NAME, "below shard recovery limit of [%d]", concurrentRecoveries);
} }
} }

View File

@ -26,6 +26,7 @@ import org.elasticsearch.client.Requests;
import org.elasticsearch.common.inject.Inject; import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.settings.SettingsFilter; import org.elasticsearch.common.settings.SettingsFilter;
import org.elasticsearch.common.xcontent.ToXContent;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.rest.*; import org.elasticsearch.rest.*;
@ -50,6 +51,7 @@ public class RestClusterRerouteAction extends BaseRestHandler {
final ClusterRerouteRequest clusterRerouteRequest = Requests.clusterRerouteRequest(); final ClusterRerouteRequest clusterRerouteRequest = Requests.clusterRerouteRequest();
clusterRerouteRequest.listenerThreaded(false); clusterRerouteRequest.listenerThreaded(false);
clusterRerouteRequest.dryRun(request.paramAsBoolean("dry_run", clusterRerouteRequest.dryRun())); clusterRerouteRequest.dryRun(request.paramAsBoolean("dry_run", clusterRerouteRequest.dryRun()));
clusterRerouteRequest.explain(request.paramAsBoolean("explain", clusterRerouteRequest.explain()));
clusterRerouteRequest.timeout(request.paramAsTime("timeout", clusterRerouteRequest.timeout())); clusterRerouteRequest.timeout(request.paramAsTime("timeout", clusterRerouteRequest.timeout()));
clusterRerouteRequest.masterNodeTimeout(request.paramAsTime("master_timeout", clusterRerouteRequest.masterNodeTimeout())); clusterRerouteRequest.masterNodeTimeout(request.paramAsTime("master_timeout", clusterRerouteRequest.masterNodeTimeout()));
if (request.hasContent()) { if (request.hasContent()) {
@ -75,6 +77,10 @@ public class RestClusterRerouteAction extends BaseRestHandler {
} }
response.getState().settingsFilter(settingsFilter).toXContent(builder, request); response.getState().settingsFilter(settingsFilter).toXContent(builder, request);
builder.endObject(); builder.endObject();
if (clusterRerouteRequest.explain()) {
assert response.getExplanations() != null;
response.getExplanations().toXContent(builder, ToXContent.EMPTY_PARAMS);
}
} }
@Override @Override

View File

@ -21,10 +21,14 @@ package org.elasticsearch.cluster.allocation;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse; import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus; import org.elasticsearch.action.admin.cluster.health.ClusterHealthStatus;
import org.elasticsearch.action.admin.cluster.reroute.ClusterRerouteResponse;
import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.routing.ShardRoutingState; import org.elasticsearch.cluster.routing.ShardRoutingState;
import org.elasticsearch.cluster.routing.allocation.RerouteExplanation;
import org.elasticsearch.cluster.routing.allocation.RoutingExplanations;
import org.elasticsearch.cluster.routing.allocation.command.AllocateAllocationCommand; import org.elasticsearch.cluster.routing.allocation.command.AllocateAllocationCommand;
import org.elasticsearch.cluster.routing.allocation.command.MoveAllocationCommand; import org.elasticsearch.cluster.routing.allocation.command.MoveAllocationCommand;
import org.elasticsearch.cluster.routing.allocation.decider.Decision;
import org.elasticsearch.cluster.routing.allocation.decider.DisableAllocationDecider; import org.elasticsearch.cluster.routing.allocation.decider.DisableAllocationDecider;
import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider; import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider;
import org.elasticsearch.common.Priority; import org.elasticsearch.common.Priority;
@ -84,6 +88,7 @@ public class ClusterRerouteTests extends ElasticsearchIntegrationTest {
logger.info("--> explicitly allocate shard 1, *under dry_run*"); logger.info("--> explicitly allocate shard 1, *under dry_run*");
state = client().admin().cluster().prepareReroute() state = client().admin().cluster().prepareReroute()
.setExplain(randomBoolean())
.add(new AllocateAllocationCommand(new ShardId("test", 0), node_1, true)) .add(new AllocateAllocationCommand(new ShardId("test", 0), node_1, true))
.setDryRun(true) .setDryRun(true)
.execute().actionGet().getState(); .execute().actionGet().getState();
@ -96,6 +101,7 @@ public class ClusterRerouteTests extends ElasticsearchIntegrationTest {
logger.info("--> explicitly allocate shard 1, actually allocating, no dry run"); logger.info("--> explicitly allocate shard 1, actually allocating, no dry run");
state = client().admin().cluster().prepareReroute() state = client().admin().cluster().prepareReroute()
.setExplain(randomBoolean())
.add(new AllocateAllocationCommand(new ShardId("test", 0), node_1, true)) .add(new AllocateAllocationCommand(new ShardId("test", 0), node_1, true))
.execute().actionGet().getState(); .execute().actionGet().getState();
assertThat(state.routingNodes().unassigned().size(), equalTo(1)); assertThat(state.routingNodes().unassigned().size(), equalTo(1));
@ -111,6 +117,7 @@ public class ClusterRerouteTests extends ElasticsearchIntegrationTest {
logger.info("--> move shard 1 primary from node1 to node2"); logger.info("--> move shard 1 primary from node1 to node2");
state = client().admin().cluster().prepareReroute() state = client().admin().cluster().prepareReroute()
.setExplain(randomBoolean())
.add(new MoveAllocationCommand(new ShardId("test", 0), node_1, node_2)) .add(new MoveAllocationCommand(new ShardId("test", 0), node_1, node_2))
.execute().actionGet().getState(); .execute().actionGet().getState();
@ -164,6 +171,7 @@ public class ClusterRerouteTests extends ElasticsearchIntegrationTest {
logger.info("--> explicitly allocate shard 1, actually allocating, no dry run"); logger.info("--> explicitly allocate shard 1, actually allocating, no dry run");
state = client().admin().cluster().prepareReroute() state = client().admin().cluster().prepareReroute()
.setExplain(randomBoolean())
.add(new AllocateAllocationCommand(new ShardId("test", 0), node_1, true)) .add(new AllocateAllocationCommand(new ShardId("test", 0), node_1, true))
.execute().actionGet().getState(); .execute().actionGet().getState();
assertThat(state.routingNodes().unassigned().size(), equalTo(1)); assertThat(state.routingNodes().unassigned().size(), equalTo(1));
@ -197,6 +205,7 @@ public class ClusterRerouteTests extends ElasticsearchIntegrationTest {
assertThat(client().admin().cluster().prepareHealth().setWaitForNodes("2").execute().actionGet().getStatus(), equalTo(ClusterHealthStatus.RED)); assertThat(client().admin().cluster().prepareHealth().setWaitForNodes("2").execute().actionGet().getStatus(), equalTo(ClusterHealthStatus.RED));
logger.info("--> explicitly allocate primary"); logger.info("--> explicitly allocate primary");
state = client().admin().cluster().prepareReroute() state = client().admin().cluster().prepareReroute()
.setExplain(randomBoolean())
.add(new AllocateAllocationCommand(new ShardId("test", 0), node_1, true)) .add(new AllocateAllocationCommand(new ShardId("test", 0), node_1, true))
.execute().actionGet().getState(); .execute().actionGet().getState();
assertThat(state.routingNodes().unassigned().size(), equalTo(1)); assertThat(state.routingNodes().unassigned().size(), equalTo(1));
@ -212,4 +221,47 @@ public class ClusterRerouteTests extends ElasticsearchIntegrationTest {
} }
@Test
public void rerouteExplain() {
Settings commonSettings = settingsBuilder().build();
logger.info("--> starting a node");
String node_1 = cluster().startNode(commonSettings);
assertThat(cluster().size(), equalTo(1));
ClusterHealthResponse healthResponse = client().admin().cluster().prepareHealth().setWaitForNodes("1").execute().actionGet();
assertThat(healthResponse.isTimedOut(), equalTo(false));
logger.info("--> create an index with 1 shard");
client().admin().indices().prepareCreate("test")
.setSettings(settingsBuilder().put("index.number_of_shards", 1).put("index.number_of_replicas", 0))
.execute().actionGet();
ensureGreen("test");
logger.info("--> disable allocation");
Settings newSettings = settingsBuilder()
.put(EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE, EnableAllocationDecider.Allocation.NONE.name())
.build();
client().admin().cluster().prepareUpdateSettings().setTransientSettings(newSettings).execute().actionGet();
logger.info("--> starting a second node");
String node_2 = cluster().startNode(commonSettings);
assertThat(cluster().size(), equalTo(2));
healthResponse = client().admin().cluster().prepareHealth().setWaitForNodes("2").execute().actionGet();
assertThat(healthResponse.isTimedOut(), equalTo(false));
logger.info("--> try to move the shard from node1 to node2");
MoveAllocationCommand cmd = new MoveAllocationCommand(new ShardId("test", 0), node_1, node_2);
ClusterRerouteResponse resp = client().admin().cluster().prepareReroute().add(cmd).setExplain(true).execute().actionGet();
RoutingExplanations e = resp.getExplanations();
assertThat(e.explanations().size(), equalTo(1));
RerouteExplanation explanation = e.explanations().get(0);
assertThat(explanation.command().name(), equalTo(cmd.name()));
assertThat(((MoveAllocationCommand)explanation.command()).shardId(), equalTo(cmd.shardId()));
assertThat(((MoveAllocationCommand)explanation.command()).fromNode(), equalTo(cmd.fromNode()));
assertThat(((MoveAllocationCommand)explanation.command()).toNode(), equalTo(cmd.toNode()));
assertThat(explanation.decisions().type(), equalTo(Decision.Type.YES));
}
} }