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

org.elasticsearch.cluster.routing.allocation.AllocateUnassignedDecision Maven / Gradle / Ivy

There is a newer version: 8.16.0
Show newest version
/*
 * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
 * or more contributor license agreements. Licensed under the Elastic License
 * 2.0 and the Server Side Public License, v 1; you may not use this file except
 * in compliance with, at your election, the Elastic License 2.0 or the Server
 * Side Public License, v 1.
 */

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.io.stream.StreamInput;
import org.elasticsearch.common.io.stream.StreamOutput;
import org.elasticsearch.core.Nullable;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.xcontent.XContentBuilder;

import java.io.IOException;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Locale;
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();
        return switch (getAllocationDecision()) {
            case YES -> Explanations.Allocation.YES;
            case THROTTLED -> Explanations.Allocation.THROTTLED;
            case AWAITING_INFO -> Explanations.Allocation.AWAITING_INFO;
            case NO_VALID_SHARD_COPY -> hasNodeWithStaleOrCorruptShard()
                ? Explanations.Allocation.ALL_COPIES_INVALID
                : Explanations.Allocation.NO_COPIES;
            case ALLOCATION_DELAYED -> String.format(
                Locale.ROOT,
                atLeastOneNodeWithYesDecision()
                    ? Explanations.Allocation.DELAYED_WITH_ALTERNATIVE
                    : Explanations.Allocation.DELAYED_WITHOUT_ALTERNATIVE,
                TimeValue.timeValueMillis(remainingDelayInMillis)
            );
            case NO -> reuseStore ? Explanations.Allocation.EXISTING_STORES_FORBIDDEN : Explanations.Allocation.ALL_NODES_FORBIDDEN;
            case WORSE_BALANCE, NO_ATTEMPT -> {
                assert false : getAllocationDecision();
                yield getAllocationDecision().toString();
            }
        };
    }

    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
        );
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy