diff options
Diffstat (limited to 'core/src/main/java/org/elasticsearch/action/admin/cluster/allocation')
6 files changed, 764 insertions, 0 deletions
diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplainAction.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplainAction.java new file mode 100644 index 0000000000..d34ac63602 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplainAction.java @@ -0,0 +1,48 @@ +/* + * 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.action.admin.cluster.allocation; + +import org.elasticsearch.action.Action; +import org.elasticsearch.client.ElasticsearchClient; + +/** + * Action for explaining shard allocation for a shard in the cluster + */ +public class ClusterAllocationExplainAction extends Action<ClusterAllocationExplainRequest, + ClusterAllocationExplainResponse, + ClusterAllocationExplainRequestBuilder> { + + public static final ClusterAllocationExplainAction INSTANCE = new ClusterAllocationExplainAction(); + public static final String NAME = "cluster:monitor/allocation/explain"; + + private ClusterAllocationExplainAction() { + super(NAME); + } + + @Override + public ClusterAllocationExplainResponse newResponse() { + return new ClusterAllocationExplainResponse(); + } + + @Override + public ClusterAllocationExplainRequestBuilder newRequestBuilder(ElasticsearchClient client) { + return new ClusterAllocationExplainRequestBuilder(client, this); + } +} diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplainRequest.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplainRequest.java new file mode 100644 index 0000000000..d14785127d --- /dev/null +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplainRequest.java @@ -0,0 +1,195 @@ +/* + * 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.action.admin.cluster.allocation; + +import org.elasticsearch.ElasticsearchParseException; +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.action.support.master.MasterNodeRequest; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.Objects; + +import static org.elasticsearch.action.ValidateActions.addValidationError; + +/** + * A request to explain the allocation of a shard in the cluster + */ +public class ClusterAllocationExplainRequest extends MasterNodeRequest<ClusterAllocationExplainRequest> { + + private String index; + private Integer shard; + private Boolean primary; + private boolean includeYesDecisions = false; + + /** Explain the first unassigned shard */ + public ClusterAllocationExplainRequest() { + this.index = null; + this.shard = null; + this.primary = null; + } + + /** + * Create a new allocation explain request. If {@code primary} is false, the first unassigned replica + * will be picked for explanation. If no replicas are unassigned, the first assigned replica will + * be explained. + */ + public ClusterAllocationExplainRequest(String index, int shard, boolean primary) { + this.index = index; + this.shard = shard; + this.primary = primary; + } + + @Override + public ActionRequestValidationException validate() { + ActionRequestValidationException validationException = null; + if (this.useAnyUnassignedShard() == false) { + if (this.index == null) { + validationException = addValidationError("index must be specified", validationException); + } + if (this.shard == null) { + validationException = addValidationError("shard must be specified", validationException); + } + if (this.primary == null) { + validationException = addValidationError("primary must be specified", validationException); + } + } + return validationException; + } + + /** + * Returns {@code true} iff the first unassigned shard is to be used + */ + public boolean useAnyUnassignedShard() { + return this.index == null && this.shard == null && this.primary == null; + } + + public ClusterAllocationExplainRequest setIndex(String index) { + this.index = index; + return this; + } + + @Nullable + public String getIndex() { + return this.index; + } + + public ClusterAllocationExplainRequest setShard(Integer shard) { + this.shard = shard; + return this; + } + + @Nullable + public int getShard() { + return this.shard; + } + + public ClusterAllocationExplainRequest setPrimary(Boolean primary) { + this.primary = primary; + return this; + } + + @Nullable + public boolean isPrimary() { + return this.primary; + } + + public void includeYesDecisions(boolean includeYesDecisions) { + this.includeYesDecisions = includeYesDecisions; + } + + /** Returns true if all decisions should be included. Otherwise only "NO" and "THROTTLE" decisions are returned */ + public boolean includeYesDecisions() { + return this.includeYesDecisions; + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder("ClusterAllocationExplainRequest["); + if (this.useAnyUnassignedShard()) { + sb.append("useAnyUnassignedShard=true"); + } else { + sb.append("index=").append(index); + sb.append(",shard=").append(shard); + sb.append(",primary?=").append(primary); + } + sb.append(",includeYesDecisions?=").append(includeYesDecisions); + return sb.toString(); + } + + public static ClusterAllocationExplainRequest parse(XContentParser parser) throws IOException { + String currentFieldName = null; + String index = null; + Integer shard = null; + Boolean primary = null; + XContentParser.Token token; + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else if (token.isValue()) { + if ("index".equals(currentFieldName)) { + index = parser.text(); + } else if ("shard".equals(currentFieldName)) { + shard = parser.intValue(); + } else if ("primary".equals(currentFieldName)) { + primary = parser.booleanValue(); + } else { + throw new ElasticsearchParseException("unexpected field [" + currentFieldName + "] in allocation explain request"); + } + + } else if (token == XContentParser.Token.START_OBJECT) { + // the object was started + continue; + } else { + throw new ElasticsearchParseException("unexpected token [" + token + "] in allocation explain request"); + } + } + + if (index == null && shard == null && primary == null) { + // If it was an empty body, use the "any unassigned shard" request + return new ClusterAllocationExplainRequest(); + } else if (index == null || shard == null || primary == null) { + throw new ElasticsearchParseException("'index', 'shard', and 'primary' must be specified in allocation explain request"); + } + return new ClusterAllocationExplainRequest(index, shard, primary); + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + this.index = in.readOptionalString(); + this.shard = in.readOptionalVInt(); + this.primary = in.readOptionalBoolean(); + this.includeYesDecisions = in.readBoolean(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + out.writeOptionalString(index); + out.writeOptionalVInt(shard); + out.writeOptionalBoolean(primary); + out.writeBoolean(includeYesDecisions); + } +} diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplainRequestBuilder.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplainRequestBuilder.java new file mode 100644 index 0000000000..1a1950c7f1 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplainRequestBuilder.java @@ -0,0 +1,66 @@ +/* + * 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.action.admin.cluster.allocation; + +import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.action.support.master.MasterNodeOperationRequestBuilder; +import org.elasticsearch.client.ElasticsearchClient; + +/** + * Builder for requests to explain the allocation of a shard in the cluster + */ +public class ClusterAllocationExplainRequestBuilder + extends MasterNodeOperationRequestBuilder<ClusterAllocationExplainRequest, + ClusterAllocationExplainResponse, + ClusterAllocationExplainRequestBuilder> { + + public ClusterAllocationExplainRequestBuilder(ElasticsearchClient client, ClusterAllocationExplainAction action) { + super(client, action, new ClusterAllocationExplainRequest()); + } + + /** The index name to use when finding the shard to explain */ + public ClusterAllocationExplainRequestBuilder setIndex(String index) { + request.setIndex(index); + return this; + } + + /** The shard number to use when finding the shard to explain */ + public ClusterAllocationExplainRequestBuilder setShard(int shard) { + request.setShard(shard); + return this; + } + + /** Whether the primary or replica should be explained */ + public ClusterAllocationExplainRequestBuilder setPrimary(boolean primary) { + request.setPrimary(primary); + return this; + } + + /** + * Signal that the first unassigned shard should be used + */ + public ClusterAllocationExplainRequestBuilder useAnyUnassignedShard() { + request.setIndex(null); + request.setShard(null); + request.setPrimary(null); + return this; + } + +} diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplainResponse.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplainResponse.java new file mode 100644 index 0000000000..cc586bd1a5 --- /dev/null +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplainResponse.java @@ -0,0 +1,61 @@ +/* + * 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.action.admin.cluster.allocation; + +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; + +import java.io.IOException; + +/** + * Explanation response for a shard in the cluster + */ +public class ClusterAllocationExplainResponse extends ActionResponse { + + private ClusterAllocationExplanation cae; + + public ClusterAllocationExplainResponse() { + } + + public ClusterAllocationExplainResponse(ClusterAllocationExplanation cae) { + this.cae = cae; + } + + /** + * Return the explanation for shard allocation in the cluster + */ + public ClusterAllocationExplanation getExplanation() { + return this.cae; + } + + @Override + public void readFrom(StreamInput in) throws IOException { + super.readFrom(in); + this.cae = new ClusterAllocationExplanation(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + super.writeTo(out); + cae.writeTo(out); + } +} diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplanation.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplanation.java new file mode 100644 index 0000000000..6b4173734b --- /dev/null +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/ClusterAllocationExplanation.java @@ -0,0 +1,200 @@ +/* + * 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.action.admin.cluster.allocation; + +import com.carrotsearch.hppc.cursors.ObjectObjectCursor; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.routing.UnassignedInfo; +import org.elasticsearch.cluster.routing.allocation.decider.Decision; +import org.elasticsearch.common.Nullable; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.xcontent.ToXContent; +import org.elasticsearch.common.xcontent.ToXContent.Params; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.index.shard.ShardId; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * A {@code ClusterAllocationExplanation} is an explanation of why a shard may or may not be allocated to nodes. It also includes weights + * for where the shard is likely to be assigned. It is an immutable class + */ +public final class ClusterAllocationExplanation implements ToXContent, Writeable<ClusterAllocationExplanation> { + + private final ShardId shard; + private final boolean primary; + private final String assignedNodeId; + private final Map<DiscoveryNode, Decision> nodeToDecision; + private final Map<DiscoveryNode, Float> nodeWeights; + private final UnassignedInfo unassignedInfo; + + public ClusterAllocationExplanation(StreamInput in) throws IOException { + this.shard = ShardId.readShardId(in); + this.primary = in.readBoolean(); + this.assignedNodeId = in.readOptionalString(); + this.unassignedInfo = in.readOptionalWriteable(UnassignedInfo::new); + + Map<DiscoveryNode, Decision> ntd = null; + int size = in.readVInt(); + ntd = new HashMap<>(size); + for (int i = 0; i < size; i++) { + DiscoveryNode dn = DiscoveryNode.readNode(in); + Decision decision = Decision.readFrom(in); + ntd.put(dn, decision); + } + this.nodeToDecision = ntd; + + Map<DiscoveryNode, Float> ntw = null; + size = in.readVInt(); + ntw = new HashMap<>(size); + for (int i = 0; i < size; i++) { + DiscoveryNode dn = DiscoveryNode.readNode(in); + float weight = in.readFloat(); + ntw.put(dn, weight); + } + this.nodeWeights = ntw; + } + + public ClusterAllocationExplanation(ShardId shard, boolean primary, @Nullable String assignedNodeId, + UnassignedInfo unassignedInfo, Map<DiscoveryNode, Decision> nodeToDecision, + Map<DiscoveryNode, Float> nodeWeights) { + this.shard = shard; + this.primary = primary; + this.assignedNodeId = assignedNodeId; + this.unassignedInfo = unassignedInfo; + this.nodeToDecision = nodeToDecision == null ? Collections.emptyMap() : nodeToDecision; + this.nodeWeights = nodeWeights == null ? Collections.emptyMap() : nodeWeights; + } + + public ShardId getShard() { + return this.shard; + } + + public boolean isPrimary() { + return this.primary; + } + + /** Return turn if the shard is assigned to a node */ + public boolean isAssigned() { + return this.assignedNodeId != null; + } + + /** Return the assigned node id or null if not assigned */ + @Nullable + public String getAssignedNodeId() { + return this.assignedNodeId; + } + + /** Return the unassigned info for the shard or null if the shard is assigned */ + @Nullable + public UnassignedInfo getUnassignedInfo() { + return this.unassignedInfo; + } + + /** Return a map of node to decision for shard allocation */ + public Map<DiscoveryNode, Decision> getNodeDecisions() { + return this.nodeToDecision; + } + + /** + * Return a map of node to balancer "weight" for allocation. Higher weights mean the balancer wants to allocated the shard to that node + * more + */ + public Map<DiscoveryNode, Float> getNodeWeights() { + return this.nodeWeights; + } + + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); { + builder.startObject("shard"); { + builder.field("index", shard.getIndexName()); + builder.field("index_uuid", shard.getIndex().getUUID()); + builder.field("id", shard.getId()); + builder.field("primary", primary); + } + builder.endObject(); // end shard + builder.field("assigned", this.assignedNodeId != null); + // If assigned, show the node id of the node it's assigned to + if (assignedNodeId != null) { + builder.field("assigned_node_id", this.assignedNodeId); + } + // If we have unassigned info, show that + if (unassignedInfo != null) { + unassignedInfo.toXContent(builder, params); + } + builder.startObject("nodes"); + for (Map.Entry<DiscoveryNode, Float> entry : nodeWeights.entrySet()) { + DiscoveryNode node = entry.getKey(); + builder.startObject(node.getId()); { + builder.field("node_name", node.getName()); + builder.startObject("node_attributes"); { + for (ObjectObjectCursor<String, String> attrKV : node.attributes()) { + builder.field(attrKV.key, attrKV.value); + } + } + builder.endObject(); // end attributes + Decision d = nodeToDecision.get(node); + if (node.getId().equals(assignedNodeId)) { + builder.field("final_decision", "CURRENTLY_ASSIGNED"); + } else { + builder.field("final_decision", d.type().toString()); + } + builder.field("weight", entry.getValue()); + d.toXContent(builder, params); + } + builder.endObject(); // end node <uuid> + } + builder.endObject(); // end nodes + } + builder.endObject(); // end wrapping object + return builder; + } + + @Override + public ClusterAllocationExplanation readFrom(StreamInput in) throws IOException { + return new ClusterAllocationExplanation(in); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + this.getShard().writeTo(out); + out.writeBoolean(this.isPrimary()); + out.writeOptionalString(this.getAssignedNodeId()); + out.writeOptionalWriteable(this.getUnassignedInfo()); + + Map<DiscoveryNode, Decision> ntd = this.getNodeDecisions(); + out.writeVInt(ntd.size()); + for (Map.Entry<DiscoveryNode, Decision> entry : ntd.entrySet()) { + entry.getKey().writeTo(out); + Decision.writeTo(entry.getValue(), out); + } + Map<DiscoveryNode, Float> ntw = this.getNodeWeights(); + out.writeVInt(ntw.size()); + for (Map.Entry<DiscoveryNode, Float> entry : ntw.entrySet()) { + entry.getKey().writeTo(out); + out.writeFloat(entry.getValue()); + } + } +} diff --git a/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/TransportClusterAllocationExplainAction.java b/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/TransportClusterAllocationExplainAction.java new file mode 100644 index 0000000000..b9b31634bb --- /dev/null +++ b/core/src/main/java/org/elasticsearch/action/admin/cluster/allocation/TransportClusterAllocationExplainAction.java @@ -0,0 +1,194 @@ +/* + * 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.action.admin.cluster.allocation; + +import com.carrotsearch.hppc.cursors.ObjectObjectCursor; +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.master.TransportMasterNodeAction; +import org.elasticsearch.cluster.ClusterInfoService; +import org.elasticsearch.cluster.ClusterName; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.block.ClusterBlockException; +import org.elasticsearch.cluster.block.ClusterBlockLevel; +import org.elasticsearch.cluster.metadata.IndexMetaData; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.metadata.MetaData; +import org.elasticsearch.cluster.metadata.MetaData.Custom; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.routing.RoutingNode; +import org.elasticsearch.cluster.routing.RoutingNodes; +import org.elasticsearch.cluster.routing.RoutingNodes.RoutingNodesIterator; +import org.elasticsearch.cluster.routing.RoutingTable; +import org.elasticsearch.cluster.routing.ShardRouting; +import org.elasticsearch.cluster.routing.UnassignedInfo; +import org.elasticsearch.cluster.routing.allocation.AllocationService; +import org.elasticsearch.cluster.routing.allocation.RoutingAllocation; +import org.elasticsearch.cluster.routing.allocation.RoutingExplanations; +import org.elasticsearch.cluster.routing.allocation.allocator.ShardsAllocator; +import org.elasticsearch.cluster.routing.allocation.decider.AllocationDeciders; +import org.elasticsearch.cluster.routing.allocation.decider.Decision; +import org.elasticsearch.cluster.service.ClusterService; +import org.elasticsearch.common.inject.Inject; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.threadpool.ThreadPool; +import org.elasticsearch.transport.TransportService; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +/** + * The {@code TransportClusterAllocationExplainAction} is responsible for actually executing the explanation of a shard's allocation on the + * master node in the cluster. + */ +public class TransportClusterAllocationExplainAction + extends TransportMasterNodeAction<ClusterAllocationExplainRequest, ClusterAllocationExplainResponse> { + + private final AllocationService allocationService; + private final ClusterInfoService clusterInfoService; + private final AllocationDeciders allocationDeciders; + private final ShardsAllocator shardAllocator; + + @Inject + public TransportClusterAllocationExplainAction(Settings settings, TransportService transportService, ClusterService clusterService, + ThreadPool threadPool, ActionFilters actionFilters, + IndexNameExpressionResolver indexNameExpressionResolver, + AllocationService allocationService, ClusterInfoService clusterInfoService, + AllocationDeciders allocationDeciders, ShardsAllocator shardAllocator) { + super(settings, ClusterAllocationExplainAction.NAME, transportService, clusterService, threadPool, actionFilters, + indexNameExpressionResolver, ClusterAllocationExplainRequest::new); + this.allocationService = allocationService; + this.clusterInfoService = clusterInfoService; + this.allocationDeciders = allocationDeciders; + this.shardAllocator = shardAllocator; + } + + @Override + protected String executor() { + return ThreadPool.Names.MANAGEMENT; + } + + @Override + protected ClusterBlockException checkBlock(ClusterAllocationExplainRequest request, ClusterState state) { + return state.blocks().globalBlockedException(ClusterBlockLevel.METADATA_READ); + } + + @Override + protected ClusterAllocationExplainResponse newResponse() { + return new ClusterAllocationExplainResponse(); + } + + /** + * Return the decisions for the given {@code ShardRouting} on the given {@code RoutingNode}. If {@code includeYesDecisions} is not true, + * only non-YES (NO and THROTTLE) decisions are returned. + */ + public static Decision tryShardOnNode(ShardRouting shard, RoutingNode node, RoutingAllocation allocation, boolean includeYesDecisions) { + Decision d = allocation.deciders().canAllocate(shard, node, allocation); + if (includeYesDecisions) { + return d; + } else { + Decision.Multi nonYesDecisions = new Decision.Multi(); + List<Decision> decisions = d.getDecisions(); + for (Decision decision : decisions) { + if (decision.type() != Decision.Type.YES) { + nonYesDecisions.add(decision); + } + } + return nonYesDecisions; + } + } + + /** + * For the given {@code ShardRouting}, return the explanation of the allocation for that shard on all nodes. If {@code + * includeYesDecisions} is true, returns all decisions, otherwise returns only 'NO' and 'THROTTLE' decisions. + */ + public static ClusterAllocationExplanation explainShard(ShardRouting shard, RoutingAllocation allocation, RoutingNodes routingNodes, + boolean includeYesDecisions, ShardsAllocator shardAllocator) { + // don't short circuit deciders, we want a full explanation + allocation.debugDecision(true); + // get the existing unassigned info if available + UnassignedInfo ui = shard.unassignedInfo(); + + RoutingNodesIterator iter = routingNodes.nodes(); + Map<DiscoveryNode, Decision> nodeToDecision = new HashMap<>(); + while (iter.hasNext()) { + RoutingNode node = iter.next(); + DiscoveryNode discoNode = node.node(); + if (discoNode.isDataNode()) { + Decision d = tryShardOnNode(shard, node, allocation, includeYesDecisions); + nodeToDecision.put(discoNode, d); + } + } + return new ClusterAllocationExplanation(shard.shardId(), shard.primary(), shard.currentNodeId(), ui, nodeToDecision, + shardAllocator.weighShard(allocation, shard)); + } + + @Override + protected void masterOperation(final ClusterAllocationExplainRequest request, final ClusterState state, + final ActionListener<ClusterAllocationExplainResponse> listener) { + final RoutingNodes routingNodes = state.getRoutingNodes(); + final RoutingAllocation allocation = new RoutingAllocation(allocationDeciders, routingNodes, state.nodes(), + clusterInfoService.getClusterInfo(), System.nanoTime()); + + ShardRouting shardRouting = null; + if (request.useAnyUnassignedShard()) { + // If we can use any shard, just pick the first unassigned one (if there are any) + RoutingNodes.UnassignedShards.UnassignedIterator ui = routingNodes.unassigned().iterator(); + if (ui.hasNext()) { + shardRouting = ui.next(); + } + } else { + String index = request.getIndex(); + int shard = request.getShard(); + if (request.isPrimary()) { + // If we're looking for the primary shard, there's only one copy, so pick it directly + shardRouting = allocation.routingTable().shardRoutingTable(index, shard).primaryShard(); + } else { + // If looking for a replica, go through all the replica shards + List<ShardRouting> replicaShardRoutings = allocation.routingTable().shardRoutingTable(index, shard).replicaShards(); + if (replicaShardRoutings.size() > 0) { + // Pick the first replica at the very least + shardRouting = replicaShardRoutings.get(0); + // In case there are multiple replicas where some are assigned and some aren't, + // try to find one that is unassigned at least + for (ShardRouting replica : replicaShardRoutings) { + if (replica.unassigned()) { + shardRouting = replica; + break; + } + } + } + } + } + + if (shardRouting == null) { + listener.onFailure(new ElasticsearchException("unable to find any shards to explain [{}] in the routing table", request)); + return; + } + logger.debug("explaining the allocation for [{}], found shard [{}]", request, shardRouting); + + ClusterAllocationExplanation cae = explainShard(shardRouting, allocation, routingNodes, + request.includeYesDecisions(), shardAllocator); + listener.onResponse(new ClusterAllocationExplainResponse(cae)); + } +} |