![JAR search and dependency download from the Maven repository](/logo.png)
org.opensearch.cluster.routing.allocation.MoveDecision Maven / Gradle / Ivy
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/
/*
* 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.
*/
/*
* Modifications Copyright OpenSearch Contributors. See
* GitHub history for details.
*/
package org.opensearch.cluster.routing.allocation;
import org.opensearch.cluster.node.DiscoveryNode;
import org.opensearch.cluster.routing.allocation.decider.Decision;
import org.opensearch.cluster.routing.allocation.decider.Decision.Type;
import org.opensearch.common.Nullable;
import org.opensearch.common.io.stream.StreamInput;
import org.opensearch.common.io.stream.StreamOutput;
import org.opensearch.common.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
/**
* Represents a decision to move a started shard, either because it is no longer allowed to remain on its current node
* or because moving it to another node will form a better cluster balance.
*/
public final class MoveDecision extends AbstractAllocationDecision {
/** a constant representing no decision taken */
public static final MoveDecision NOT_TAKEN = new MoveDecision(null, null, AllocationDecision.NO_ATTEMPT, null, null, 0);
/** cached decisions so we don't have to recreate objects for common decisions when not in explain mode. */
private static final MoveDecision CACHED_STAY_DECISION = new MoveDecision(
Decision.YES,
null,
AllocationDecision.NO_ATTEMPT,
null,
null,
0
);
private static final MoveDecision CACHED_CANNOT_MOVE_DECISION = new MoveDecision(
Decision.NO,
null,
AllocationDecision.NO,
null,
null,
0
);
@Nullable
AllocationDecision allocationDecision;
@Nullable
private final Decision canRemainDecision;
@Nullable
private final Decision clusterRebalanceDecision;
private final int currentNodeRanking;
private MoveDecision(
Decision canRemainDecision,
Decision clusterRebalanceDecision,
AllocationDecision allocationDecision,
DiscoveryNode assignedNode,
List nodeDecisions,
int currentNodeRanking
) {
super(assignedNode, nodeDecisions);
this.allocationDecision = allocationDecision;
this.canRemainDecision = canRemainDecision;
this.clusterRebalanceDecision = clusterRebalanceDecision;
this.currentNodeRanking = currentNodeRanking;
}
public MoveDecision(StreamInput in) throws IOException {
super(in);
allocationDecision = in.readOptionalWriteable(AllocationDecision::readFrom);
canRemainDecision = in.readOptionalWriteable(Decision::readFrom);
clusterRebalanceDecision = in.readOptionalWriteable(Decision::readFrom);
currentNodeRanking = in.readVInt();
}
@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
out.writeOptionalWriteable(allocationDecision);
out.writeOptionalWriteable(canRemainDecision);
out.writeOptionalWriteable(clusterRebalanceDecision);
out.writeVInt(currentNodeRanking);
}
/**
* Creates a move decision for the shard being able to remain on its current node, so the shard won't
* be forced to move to another node.
*/
public static MoveDecision stay(Decision canRemainDecision) {
if (canRemainDecision != null) {
assert canRemainDecision.type() != Type.NO;
return new MoveDecision(canRemainDecision, null, AllocationDecision.NO_ATTEMPT, null, null, 0);
} else {
return CACHED_STAY_DECISION;
}
}
/**
* Creates a move decision for the shard not being allowed to remain on its current node.
*
* @param canRemainDecision the decision for whether the shard is allowed to remain on its current node
* @param allocationDecision the {@link AllocationDecision} for moving the shard to another node
* @param assignedNode the node where the shard should move to
* @param nodeDecisions the node-level decisions that comprised the final decision, non-null iff explain is true
* @return the {@link MoveDecision} for moving the shard to another node
*/
public static MoveDecision cannotRemain(
Decision canRemainDecision,
AllocationDecision allocationDecision,
DiscoveryNode assignedNode,
List nodeDecisions
) {
assert canRemainDecision != null;
assert canRemainDecision.type() != Type.YES : "create decision with MoveDecision#stay instead";
if (nodeDecisions == null && allocationDecision == AllocationDecision.NO) {
// the final decision is NO (no node to move the shard to) and we are not in explain mode, return a cached version
return CACHED_CANNOT_MOVE_DECISION;
} else {
assert ((assignedNode == null) == (allocationDecision != AllocationDecision.YES));
return new MoveDecision(canRemainDecision, null, allocationDecision, assignedNode, nodeDecisions, 0);
}
}
/**
* Creates a move decision for when rebalancing the shard is not allowed.
*/
public static MoveDecision cannotRebalance(
Decision canRebalanceDecision,
AllocationDecision allocationDecision,
int currentNodeRanking,
List nodeDecisions
) {
return new MoveDecision(null, canRebalanceDecision, allocationDecision, null, nodeDecisions, currentNodeRanking);
}
/**
* Creates a decision for whether to move the shard to a different node to form a better cluster balance.
*/
public static MoveDecision rebalance(
Decision canRebalanceDecision,
AllocationDecision allocationDecision,
@Nullable DiscoveryNode assignedNode,
int currentNodeRanking,
List nodeDecisions
) {
return new MoveDecision(null, canRebalanceDecision, allocationDecision, assignedNode, nodeDecisions, currentNodeRanking);
}
@Override
public boolean isDecisionTaken() {
return canRemainDecision != null || clusterRebalanceDecision != null;
}
/**
* Creates a new move decision from this decision, plus adding a remain decision.
*/
public MoveDecision withRemainDecision(Decision canRemainDecision) {
return new MoveDecision(
canRemainDecision,
clusterRebalanceDecision,
allocationDecision,
targetNode,
nodeDecisions,
currentNodeRanking
);
}
/**
* Returns {@code true} if the shard cannot remain on its current node and can be moved,
* returns {@code false} otherwise. If {@link #isDecisionTaken()} returns {@code false},
* then invoking this method will throw an {@code IllegalStateException}.
*/
public boolean forceMove() {
checkDecisionState();
return canRemain() == false && allocationDecision == AllocationDecision.YES;
}
/**
* Returns {@code true} if the shard can remain on its current node, returns {@code false} otherwise.
* If {@link #isDecisionTaken()} returns {@code false}, then invoking this method will throw an {@code IllegalStateException}.
*/
public boolean canRemain() {
checkDecisionState();
return canRemainDecision.type() == Type.YES;
}
/**
* Returns the decision for the shard being allowed to remain on its current node. If {@link #isDecisionTaken()}
* returns {@code false}, then invoking this method will throw an {@code IllegalStateException}.
*/
public Decision getCanRemainDecision() {
checkDecisionState();
return canRemainDecision;
}
/**
* Returns {@code true} if the shard is allowed to be rebalanced to another node in the cluster,
* returns {@code false} otherwise. If {@link #getClusterRebalanceDecision()} returns {@code null}, then
* the result of this method is meaningless, as no rebalance decision was taken. If {@link #isDecisionTaken()}
* returns {@code false}, then invoking this method will throw an {@code IllegalStateException}.
*/
public boolean canRebalanceCluster() {
checkDecisionState();
return clusterRebalanceDecision != null && clusterRebalanceDecision.type() == Type.YES;
}
/**
* Returns the decision for being allowed to rebalance the shard. Invoking this method will return
* {@code null} if {@link #canRemain()} ()} returns {@code false}, which means the node is not allowed to
* remain on its current node, so the cluster is forced to attempt to move the shard to a different node,
* as opposed to attempting to rebalance the shard if a better cluster balance is possible by moving it.
* If {@link #isDecisionTaken()} returns {@code false}, then invoking this method will throw an
* {@code IllegalStateException}.
*/
@Nullable
public Decision getClusterRebalanceDecision() {
checkDecisionState();
return clusterRebalanceDecision;
}
/**
* Returns the {@link AllocationDecision} for moving this shard to another node. If {@link #isDecisionTaken()} returns
* {@code false}, then invoking this method will throw an {@code IllegalStateException}.
*/
@Nullable
public AllocationDecision getAllocationDecision() {
return allocationDecision;
}
/**
* Gets the current ranking of the node to which the shard is currently assigned, relative to the
* other nodes in the cluster as reported in {@link NodeAllocationResult#getWeightRanking()}. The
* ranking will only return a meaningful positive integer if {@link #getClusterRebalanceDecision()} returns
* a non-null value; otherwise, 0 will be returned. If {@link #isDecisionTaken()} returns
* {@code false}, then invoking this method will throw an {@code IllegalStateException}.
*/
public int getCurrentNodeRanking() {
checkDecisionState();
return currentNodeRanking;
}
@Override
public String getExplanation() {
checkDecisionState();
String explanation;
if (clusterRebalanceDecision != null) {
// it was a decision to rebalance the shard, because the shard was allowed to remain on its current node
if (allocationDecision == AllocationDecision.AWAITING_INFO) {
explanation = "cannot rebalance as information about existing copies of this shard in the cluster is still being gathered";
} else if (clusterRebalanceDecision.type() == Type.NO) {
explanation = "rebalancing is not allowed"
+ (atLeastOneNodeWithYesDecision()
? ", even though there " + "is at least one node on which the shard can be allocated"
: "");
} else if (clusterRebalanceDecision.type() == Type.THROTTLE) {
explanation = "rebalancing is throttled";
} else {
assert clusterRebalanceDecision.type() == Type.YES;
if (getTargetNode() != null) {
if (allocationDecision == AllocationDecision.THROTTLED) {
explanation = "shard rebalancing throttled";
} else {
explanation = "can rebalance shard";
}
} else {
explanation = "cannot rebalance as no target node exists that can both allocate this shard "
+ "and improve the cluster balance";
}
}
} else {
// it was a decision to force move the shard
assert canRemain() == false;
if (allocationDecision == AllocationDecision.YES) {
explanation = "shard cannot remain on this node and is force-moved to another node";
} else if (allocationDecision == AllocationDecision.THROTTLED) {
explanation = "shard cannot remain on this node but is throttled on moving to another node";
} else {
assert allocationDecision == AllocationDecision.NO;
explanation = "cannot move shard to another node, even though it is not allowed to remain on its current node";
}
}
return explanation;
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
checkDecisionState();
if (targetNode != null) {
builder.startObject("target_node");
discoveryNodeToXContent(targetNode, true, builder);
builder.endObject();
}
builder.field("can_remain_on_current_node", canRemain() ? "yes" : "no");
if (canRemain() == false && canRemainDecision.getDecisions().isEmpty() == false) {
builder.startArray("can_remain_decisions");
canRemainDecision.toXContent(builder, params);
builder.endArray();
}
if (clusterRebalanceDecision != null) {
AllocationDecision rebalanceDecision = AllocationDecision.fromDecisionType(clusterRebalanceDecision.type());
builder.field("can_rebalance_cluster", rebalanceDecision);
if (rebalanceDecision != AllocationDecision.YES && clusterRebalanceDecision.getDecisions().isEmpty() == false) {
builder.startArray("can_rebalance_cluster_decisions");
clusterRebalanceDecision.toXContent(builder, params);
builder.endArray();
}
}
if (clusterRebalanceDecision != null) {
builder.field("can_rebalance_to_other_node", allocationDecision);
builder.field("rebalance_explanation", getExplanation());
} else {
builder.field("can_move_to_other_node", forceMove() ? "yes" : "no");
builder.field("move_explanation", getExplanation());
}
nodeDecisionsToXContent(nodeDecisions, builder, params);
return builder;
}
@Override
public boolean equals(Object other) {
if (super.equals(other) == false) {
return false;
}
if (other instanceof MoveDecision == false) {
return false;
}
MoveDecision that = (MoveDecision) other;
return Objects.equals(allocationDecision, that.allocationDecision)
&& Objects.equals(canRemainDecision, that.canRemainDecision)
&& Objects.equals(clusterRebalanceDecision, that.clusterRebalanceDecision)
&& currentNodeRanking == that.currentNodeRanking;
}
@Override
public int hashCode() {
return 31 * super.hashCode() + Objects.hash(allocationDecision, canRemainDecision, clusterRebalanceDecision, currentNodeRanking);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy