org.elasticsearch.cluster.routing.allocation.AllocateUnassignedDecision Maven / Gradle / Ivy
/*
* 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.timeValueField("configured_delay_in_millis", "configured_delay", TimeValue.timeValueMillis(configuredDelayInMillis));
builder.timeValueField("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;
}
@SuppressWarnings("unchecked") 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);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy