org.elasticsearch.cluster.routing.allocation.AllocateUnassignedDecision Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* 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.node.DiscoveryNode;
import org.elasticsearch.cluster.routing.UnassignedInfo.AllocationStatus;
import org.elasticsearch.cluster.routing.allocation.decider.Decision;
import org.elasticsearch.cluster.routing.allocation.decider.Decision.Type;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* Represents the allocation decision by an allocator for an unassigned shard.
*/
public class AllocateUnassignedDecision extends AbstractAllocationDecision {
/** a constant representing a shard decision where no decision was taken */
public static final AllocateUnassignedDecision NOT_TAKEN =
new AllocateUnassignedDecision(AllocationStatus.NO_ATTEMPT, null, null, null, false, 0L, 0L);
/**
* a map of cached common no/throttle decisions that don't need explanations,
* this helps prevent unnecessary object allocations for the non-explain API case
*/
private static final Map CACHED_DECISIONS;
static {
Map cachedDecisions = new EnumMap<>(AllocationStatus.class);
cachedDecisions.put(AllocationStatus.FETCHING_SHARD_DATA,
new AllocateUnassignedDecision(AllocationStatus.FETCHING_SHARD_DATA, null, null, null, false, 0L, 0L));
cachedDecisions.put(AllocationStatus.NO_VALID_SHARD_COPY,
new AllocateUnassignedDecision(AllocationStatus.NO_VALID_SHARD_COPY, null, null, null, false, 0L, 0L));
cachedDecisions.put(AllocationStatus.DECIDERS_NO,
new AllocateUnassignedDecision(AllocationStatus.DECIDERS_NO, null, null, null, false, 0L, 0L));
cachedDecisions.put(AllocationStatus.DECIDERS_THROTTLED,
new AllocateUnassignedDecision(AllocationStatus.DECIDERS_THROTTLED, null, null, null, false, 0L, 0L));
cachedDecisions.put(AllocationStatus.DELAYED_ALLOCATION,
new AllocateUnassignedDecision(AllocationStatus.DELAYED_ALLOCATION, null, null, null, false, 0L, 0L));
CACHED_DECISIONS = Collections.unmodifiableMap(cachedDecisions);
}
@Nullable
private final AllocationStatus allocationStatus;
@Nullable
private final String allocationId;
private final boolean reuseStore;
private final long remainingDelayInMillis;
private final long configuredDelayInMillis;
private AllocateUnassignedDecision(AllocationStatus allocationStatus,
DiscoveryNode assignedNode,
String allocationId,
List nodeDecisions,
boolean reuseStore,
long remainingDelayInMillis,
long configuredDelayInMillis) {
super(assignedNode, nodeDecisions);
assert assignedNode != null || allocationStatus != null :
"a yes decision must have a node to assign the shard to";
assert allocationId == null || assignedNode != null :
"allocation id can only be null if the assigned node is null";
this.allocationStatus = allocationStatus;
this.allocationId = allocationId;
this.reuseStore = reuseStore;
this.remainingDelayInMillis = remainingDelayInMillis;
this.configuredDelayInMillis = configuredDelayInMillis;
}
public AllocateUnassignedDecision(StreamInput in) throws IOException {
super(in);
allocationStatus = in.readOptionalWriteable(AllocationStatus::readFrom);
allocationId = in.readOptionalString();
reuseStore = in.readBoolean();
remainingDelayInMillis = in.readVLong();
configuredDelayInMillis = in.readVLong();
}
/**
* Returns a NO decision with the given {@link AllocationStatus}, and the individual node-level
* decisions that comprised the final NO decision if in explain mode.
*/
public static AllocateUnassignedDecision no(AllocationStatus allocationStatus, @Nullable List decisions) {
return no(allocationStatus, decisions, false);
}
/**
* Returns a NO decision for a delayed shard allocation on a replica shard, with the individual node-level
* decisions that comprised the final NO decision, if in explain mode. Instances created with this
* method will return {@link AllocationStatus#DELAYED_ALLOCATION} for {@link #getAllocationStatus()}.
*/
public static AllocateUnassignedDecision delayed(long remainingDelay, long totalDelay,
@Nullable List decisions) {
return no(AllocationStatus.DELAYED_ALLOCATION, decisions, false, remainingDelay, totalDelay);
}
/**
* Returns a NO decision with the given {@link AllocationStatus}, and the individual node-level
* decisions that comprised the final NO decision if in explain mode.
*/
public static AllocateUnassignedDecision no(AllocationStatus allocationStatus, @Nullable List decisions,
boolean reuseStore) {
return no(allocationStatus, decisions, reuseStore, 0L, 0L);
}
private static AllocateUnassignedDecision no(AllocationStatus allocationStatus, @Nullable List decisions,
boolean reuseStore, long remainingDelay, long totalDelay) {
if (decisions != null) {
return new AllocateUnassignedDecision(allocationStatus, null, null, decisions, reuseStore, remainingDelay, totalDelay);
} else {
return getCachedDecision(allocationStatus);
}
}
/**
* Returns a THROTTLE decision, with the individual node-level decisions that
* comprised the final THROTTLE decision if in explain mode.
*/
public static AllocateUnassignedDecision throttle(@Nullable List decisions) {
if (decisions != null) {
return new AllocateUnassignedDecision(AllocationStatus.DECIDERS_THROTTLED, null, null, decisions, false, 0L, 0L);
} else {
return getCachedDecision(AllocationStatus.DECIDERS_THROTTLED);
}
}
/**
* Creates a YES decision with the given individual node-level decisions that
* comprised the final YES decision, along with the node id to which the shard is assigned and
* the allocation id for the shard, if available.
*/
public static AllocateUnassignedDecision yes(DiscoveryNode assignedNode, @Nullable String allocationId,
@Nullable List decisions, boolean reuseStore) {
return new AllocateUnassignedDecision(null, assignedNode, allocationId, decisions, reuseStore, 0L, 0L);
}
/**
* Creates a {@link AllocateUnassignedDecision} from the given {@link Decision} and the assigned node, if any.
*/
public static AllocateUnassignedDecision fromDecision(Decision decision, @Nullable DiscoveryNode assignedNode,
@Nullable List nodeDecisions) {
final Type decisionType = decision.type();
AllocationStatus allocationStatus = decisionType != Type.YES ? AllocationStatus.fromDecision(decisionType) : null;
return new AllocateUnassignedDecision(allocationStatus, assignedNode, null, nodeDecisions, false, 0L, 0L);
}
private static AllocateUnassignedDecision getCachedDecision(AllocationStatus allocationStatus) {
AllocateUnassignedDecision decision = CACHED_DECISIONS.get(allocationStatus);
return Objects.requireNonNull(decision, "precomputed decision not found for " + allocationStatus);
}
@Override
public boolean isDecisionTaken() {
return allocationStatus != AllocationStatus.NO_ATTEMPT;
}
/**
* Returns the {@link AllocationDecision} denoting the result of an allocation attempt.
* If {@link #isDecisionTaken()} returns {@code false}, then invoking this method will
* throw an {@code IllegalStateException}.
*/
public AllocationDecision getAllocationDecision() {
checkDecisionState();
return AllocationDecision.fromAllocationStatus(allocationStatus);
}
/**
* Returns the status of an unsuccessful allocation attempt. This value will be {@code null} if
* no decision was taken or if the decision was {@link Decision.Type#YES}. If {@link #isDecisionTaken()}
* returns {@code false}, then invoking this method will throw an {@code IllegalStateException}.
*/
@Nullable
public AllocationStatus getAllocationStatus() {
checkDecisionState();
return allocationStatus;
}
/**
* Gets the allocation id for the existing shard copy that the allocator is assigning the shard to.
* This method returns a non-null value iff {@link #getTargetNode()} returns a non-null value
* and the node on which the shard is assigned already has a shard copy with an in-sync allocation id
* that we can re-use. If {@link #isDecisionTaken()} returns {@code false}, then invoking this method
* will throw an {@code IllegalStateException}.
*/
@Nullable
public String getAllocationId() {
checkDecisionState();
return allocationId;
}
/**
* Gets the remaining delay for allocating the replica shard when a node holding the replica left
* the cluster and the deciders are waiting to see if the node returns before allocating the replica
* elsewhere. Only returns a meaningful positive value if {@link #getAllocationStatus()} returns
* {@link AllocationStatus#DELAYED_ALLOCATION}. If {@link #isDecisionTaken()} returns {@code false},
* then invoking this method will throw an {@code IllegalStateException}.
*/
public long getRemainingDelayInMillis() {
checkDecisionState();
return remainingDelayInMillis;
}
/**
* Gets the total configured delay for allocating the replica shard when a node holding the replica left
* the cluster and the deciders are waiting to see if the node returns before allocating the replica
* elsewhere. Only returns a meaningful positive value if {@link #getAllocationStatus()} returns
* {@link AllocationStatus#DELAYED_ALLOCATION}. If {@link #isDecisionTaken()} returns {@code false},
* then invoking this method will throw an {@code IllegalStateException}.
*/
public long getConfiguredDelayInMillis() {
checkDecisionState();
return configuredDelayInMillis;
}
@Override
public String getExplanation() {
checkDecisionState();
AllocationDecision allocationDecision = getAllocationDecision();
if (allocationDecision == AllocationDecision.YES) {
return "can allocate the shard";
} else if (allocationDecision == AllocationDecision.THROTTLED) {
return "allocation temporarily throttled";
} else if (allocationDecision == AllocationDecision.AWAITING_INFO) {
return "cannot allocate because information about existing shard data is still being retrieved from some of the nodes";
} else if (allocationDecision == AllocationDecision.NO_VALID_SHARD_COPY) {
if (hasNodeWithStaleOrCorruptShard()) {
return "cannot allocate because all found copies of the shard are either stale or corrupt";
} else {
return "cannot allocate because a previous copy of the primary shard existed but can no longer be found on " +
"the nodes in the cluster";
}
} else if (allocationDecision == AllocationDecision.ALLOCATION_DELAYED) {
return "cannot allocate because the cluster is still waiting " +
TimeValue.timeValueMillis(remainingDelayInMillis) +
" for the departed node holding a replica to rejoin" +
(atLeastOneNodeWithYesDecision() ?
", despite being allowed to allocate the shard to at least one other node" : "");
} else {
assert allocationDecision == AllocationDecision.NO;
if (reuseStore) {
return "cannot allocate because allocation is not permitted to any of the nodes that hold an in-sync shard copy";
} else {
return "cannot allocate because allocation is not permitted to any of the nodes";
}
}
}
private boolean hasNodeWithStaleOrCorruptShard() {
return getNodeDecisions() != null && getNodeDecisions().stream().anyMatch(result ->
result.getShardStoreInfo() != null
&& (result.getShardStoreInfo().getAllocationId() != null
|| result.getShardStoreInfo().getStoreException() != null));
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
checkDecisionState();
builder.field("can_allocate", getAllocationDecision());
builder.field("allocate_explanation", getExplanation());
if (targetNode != null) {
builder.startObject("target_node");
discoveryNodeToXContent(targetNode, true, builder);
builder.endObject();
}
if (allocationId != null) {
builder.field("allocation_id", allocationId);
}
if (allocationStatus == AllocationStatus.DELAYED_ALLOCATION) {
builder.humanReadableField("configured_delay_in_millis", "configured_delay",
TimeValue.timeValueMillis(configuredDelayInMillis));
builder.humanReadableField("remaining_delay_in_millis", "remaining_delay",
TimeValue.timeValueMillis(remainingDelayInMillis));
}
nodeDecisionsToXContent(nodeDecisions, builder, params);
return builder;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeOptionalWriteable(allocationStatus);
out.writeOptionalString(allocationId);
out.writeBoolean(reuseStore);
out.writeVLong(remainingDelayInMillis);
out.writeVLong(configuredDelayInMillis);
}
@Override
public boolean equals(Object other) {
if (super.equals(other) == false) {
return false;
}
if (other instanceof AllocateUnassignedDecision == false) {
return false;
}
AllocateUnassignedDecision that = (AllocateUnassignedDecision) other;
return Objects.equals(allocationStatus, that.allocationStatus)
&& Objects.equals(allocationId, that.allocationId)
&& reuseStore == that.reuseStore
&& configuredDelayInMillis == that.configuredDelayInMillis
&& remainingDelayInMillis == that.remainingDelayInMillis;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + Objects.hash(allocationStatus, allocationId, reuseStore,
configuredDelayInMillis, remainingDelayInMillis);
}
}