All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.opensearch.cluster.routing.allocation.MoveDecision Maven / Gradle / Ivy

There is a newer version: 2.18.0
Show newest version
/*
 * 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.annotation.PublicApi;
import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.core.common.io.stream.StreamOutput;
import org.opensearch.core.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.
 *
 * @opensearch.api
 */
@PublicApi(since = "1.0.0")
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 - 2024 Weber Informatics LLC | Privacy Policy