org.opensearch.cluster.routing.allocation.decider.Decision Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of opensearch Show documentation
Show all versions of opensearch Show documentation
OpenSearch subproject :server
/*
* 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.decider;
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.common.io.stream.Writeable;
import org.opensearch.core.xcontent.ToXContent;
import org.opensearch.core.xcontent.ToXContentFragment;
import org.opensearch.core.xcontent.ToXContentObject;
import org.opensearch.core.xcontent.XContentBuilder;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
/**
* This abstract class defining basic {@link Decision} used during shard
* allocation process.
*
* @see AllocationDecider
*
* @opensearch.api
*/
@PublicApi(since = "1.0.0")
public abstract class Decision implements ToXContent, Writeable {
public static final Decision ALWAYS = new Single(Type.YES);
public static final Decision YES = new Single(Type.YES);
public static final Decision NO = new Single(Type.NO);
public static final Decision THROTTLE = new Single(Type.THROTTLE);
/**
* Creates a simple decision
* @param type {@link Type} of the decision
* @param label label for the Decider that produced this decision
* @param explanation explanation of the decision
* @param explanationParams additional parameters for the decision
* @return new {@link Decision} instance
*/
public static Decision single(Type type, @Nullable String label, @Nullable String explanation, @Nullable Object... explanationParams) {
return new Single(type, label, explanation, explanationParams);
}
public static Decision readFrom(StreamInput in) throws IOException {
// Determine whether to read a Single or Multi Decision
if (in.readBoolean()) {
Multi result = new Multi();
int decisionCount = in.readVInt();
for (int i = 0; i < decisionCount; i++) {
Decision s = readFrom(in);
result.decisions.add(s);
}
return result;
} else {
Single result = new Single();
result.type = Type.readFrom(in);
result.label = in.readOptionalString();
result.explanationString = in.readOptionalString();
return result;
}
}
/**
* This enumeration defines the
* possible types of decisions
*
* @opensearch.api
*/
@PublicApi(since = "1.0.0")
public enum Type implements Writeable {
YES(1),
THROTTLE(2),
NO(0);
private final int id;
Type(int id) {
this.id = id;
}
public static Type readFrom(StreamInput in) throws IOException {
int i = in.readVInt();
switch (i) {
case 0:
return NO;
case 1:
return YES;
case 2:
return THROTTLE;
default:
throw new IllegalArgumentException("No Type for integer [" + i + "]");
}
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeVInt(id);
}
public boolean higherThan(Type other) {
if (this == NO) {
return false;
} else if (other == NO) {
return true;
} else if (other == THROTTLE && this == YES) {
return true;
}
return false;
}
public boolean canPreemptivelyReturn() {
return this == THROTTLE || this == NO;
}
}
/**
* Get the {@link Type} of this decision
* @return {@link Type} of this decision
*/
public abstract Type type();
/**
* Get the description label for this decision.
*/
@Nullable
public abstract String label();
/**
* Get the explanation for this decision.
*/
@Nullable
public abstract String getExplanation();
/**
* Return the list of all decisions that make up this decision
*/
public abstract List getDecisions();
/**
* Simple class representing a single decision
*
* @opensearch.internal
*/
public static class Single extends Decision implements ToXContentObject {
private Type type;
private String label;
private String explanation;
private String explanationString;
private Object[] explanationParams;
public Single() {
}
/**
* Creates a new {@link Single} decision of a given type
* @param type {@link Type} of the decision
*/
public Single(Type type) {
this(type, null, null, (Object[]) null);
}
/**
* Creates a new {@link Single} decision of a given type
*
* @param type {@link Type} of the decision
* @param explanation An explanation of this {@link Decision}
* @param explanationParams A set of additional parameters
*/
public Single(Type type, @Nullable String label, @Nullable String explanation, @Nullable Object... explanationParams) {
this.type = type;
this.label = label;
this.explanation = explanation;
this.explanationParams = explanationParams;
}
@Override
public Type type() {
return this.type;
}
@Override
@Nullable
public String label() {
return this.label;
}
@Override
public List getDecisions() {
return Collections.singletonList(this);
}
/**
* Returns the explanation string, fully formatted. Only formats the string once.
*/
@Override
@Nullable
public String getExplanation() {
if (explanationString == null && explanation != null) {
explanationString = String.format(Locale.ROOT, explanation, explanationParams);
}
return this.explanationString;
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
Decision.Single s = (Decision.Single) object;
return this.type == s.type && Objects.equals(label, s.label) && Objects.equals(getExplanation(), s.getExplanation());
}
@Override
public int hashCode() {
int result = type.hashCode();
result = 31 * result + (label == null ? 0 : label.hashCode());
String explanationStr = getExplanation();
result = 31 * result + (explanationStr == null ? 0 : explanationStr.hashCode());
return result;
}
@Override
public String toString() {
if (explanationString != null || explanation != null) {
return type + "(" + getExplanation() + ")";
}
return type + "()";
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
builder.field("decider", label);
builder.field("decision", type);
String explanation = getExplanation();
builder.field("explanation", explanation != null ? explanation : "none");
builder.endObject();
return builder;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeBoolean(false); // flag specifying its a single decision
type.writeTo(out);
out.writeOptionalString(label);
// Flatten explanation on serialization, so that explanationParams
// do not need to be serialized
out.writeOptionalString(getExplanation());
}
}
/**
* Simple class representing a list of decisions
*
* @opensearch.internal
*/
public static class Multi extends Decision implements ToXContentFragment {
private final List decisions = new ArrayList<>();
/**
* Add a decision to this {@link Multi}decision instance
* @param decision {@link Decision} to add
* @return {@link Multi}decision instance with the given decision added
*/
public Multi add(Decision decision) {
decisions.add(decision);
return this;
}
@Override
public Type type() {
Type ret = Type.YES;
for (int i = 0; i < decisions.size(); i++) {
Type type = decisions.get(i).type();
if (type == Type.NO) {
return type;
} else if (type == Type.THROTTLE) {
ret = type;
}
}
return ret;
}
@Override
@Nullable
public String label() {
// Multi decisions have no labels
return null;
}
@Override
@Nullable
public String getExplanation() {
throw new UnsupportedOperationException("multi-level decisions do not have an explanation");
}
@Override
public List getDecisions() {
return Collections.unmodifiableList(this.decisions);
}
@Override
public boolean equals(final Object object) {
if (this == object) {
return true;
}
if (object == null || getClass() != object.getClass()) {
return false;
}
final Decision.Multi m = (Decision.Multi) object;
return this.decisions.equals(m.decisions);
}
@Override
public int hashCode() {
return 31 * decisions.hashCode();
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Decision decision : decisions) {
sb.append("[").append(decision.toString()).append("]");
}
return sb.toString();
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
for (Decision d : decisions) {
d.toXContent(builder, params);
}
return builder;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeBoolean(true); // flag indicating it is a multi decision
out.writeVInt(getDecisions().size());
for (Decision d : getDecisions()) {
d.writeTo(out);
}
}
}
}