org.elasticsearch.cluster.metadata.SingleNodeShutdownMetadata Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of elasticsearch Show documentation
Show all versions of elasticsearch Show documentation
Elasticsearch subproject :server
/*
* 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.metadata;
import org.apache.logging.log4j.message.ParameterizedMessage;
import org.elasticsearch.Version;
import org.elasticsearch.cluster.AbstractDiffable;
import org.elasticsearch.cluster.Diffable;
import org.elasticsearch.common.Strings;
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.ConstructingObjectParser;
import org.elasticsearch.xcontent.ObjectParser;
import org.elasticsearch.xcontent.ParseField;
import org.elasticsearch.xcontent.ToXContentObject;
import org.elasticsearch.xcontent.XContentBuilder;
import org.elasticsearch.xcontent.XContentParser;
import java.io.IOException;
import java.util.Locale;
import java.util.Objects;
/**
* Contains data about a single node's shutdown readiness.
*/
public class SingleNodeShutdownMetadata extends AbstractDiffable
implements
ToXContentObject,
Diffable {
public static final Version REPLACE_SHUTDOWN_TYPE_ADDED_VERSION = Version.V_7_16_0;
public static final ParseField NODE_ID_FIELD = new ParseField("node_id");
public static final ParseField TYPE_FIELD = new ParseField("type");
public static final ParseField REASON_FIELD = new ParseField("reason");
public static final String STARTED_AT_READABLE_FIELD = "shutdown_started";
public static final ParseField STARTED_AT_MILLIS_FIELD = new ParseField(STARTED_AT_READABLE_FIELD + "millis");
public static final ParseField ALLOCATION_DELAY_FIELD = new ParseField("allocation_delay");
public static final ParseField NODE_SEEN_FIELD = new ParseField("node_seen");
public static final ParseField TARGET_NODE_NAME_FIELD = new ParseField("target_node_name");
public static final ConstructingObjectParser PARSER = new ConstructingObjectParser<>(
"node_shutdown_info",
a -> new SingleNodeShutdownMetadata(
(String) a[0],
Type.valueOf((String) a[1]),
(String) a[2],
(long) a[3],
(boolean) a[4],
(TimeValue) a[5],
(String) a[6]
)
);
static {
PARSER.declareString(ConstructingObjectParser.constructorArg(), NODE_ID_FIELD);
PARSER.declareString(ConstructingObjectParser.constructorArg(), TYPE_FIELD);
PARSER.declareString(ConstructingObjectParser.constructorArg(), REASON_FIELD);
PARSER.declareLong(ConstructingObjectParser.constructorArg(), STARTED_AT_MILLIS_FIELD);
PARSER.declareBoolean(ConstructingObjectParser.constructorArg(), NODE_SEEN_FIELD);
PARSER.declareField(
ConstructingObjectParser.optionalConstructorArg(),
(p, c) -> TimeValue.parseTimeValue(p.textOrNull(), ALLOCATION_DELAY_FIELD.getPreferredName()),
ALLOCATION_DELAY_FIELD,
ObjectParser.ValueType.STRING_OR_NULL
);
PARSER.declareString(ConstructingObjectParser.optionalConstructorArg(), TARGET_NODE_NAME_FIELD);
}
public static SingleNodeShutdownMetadata parse(XContentParser parser) {
return PARSER.apply(parser, null);
}
public static final TimeValue DEFAULT_RESTART_SHARD_ALLOCATION_DELAY = TimeValue.timeValueMinutes(5);
private final String nodeId;
private final Type type;
private final String reason;
private final long startedAtMillis;
private final boolean nodeSeen;
@Nullable
private final TimeValue allocationDelay;
@Nullable
private final String targetNodeName;
/**
* @param nodeId The node ID that this shutdown metadata refers to.
* @param type The type of shutdown. See {@link Type}.
* @param reason The reason for the shutdown, per the original shutdown request.
* @param startedAtMillis The timestamp at which this shutdown was requested.
*/
private SingleNodeShutdownMetadata(
String nodeId,
Type type,
String reason,
long startedAtMillis,
boolean nodeSeen,
@Nullable TimeValue allocationDelay,
@Nullable String targetNodeName
) {
this.nodeId = Objects.requireNonNull(nodeId, "node ID must not be null");
this.type = Objects.requireNonNull(type, "shutdown type must not be null");
this.reason = Objects.requireNonNull(reason, "shutdown reason must not be null");
this.startedAtMillis = startedAtMillis;
this.nodeSeen = nodeSeen;
if (allocationDelay != null && Type.RESTART.equals(type) == false) {
throw new IllegalArgumentException("shard allocation delay is only valid for RESTART-type shutdowns");
}
this.allocationDelay = allocationDelay;
if (targetNodeName != null && type != Type.REPLACE) {
throw new IllegalArgumentException(
new ParameterizedMessage(
"target node name is only valid for REPLACE type shutdowns, " + "but was given type [{}] and target node name [{}]",
type,
targetNodeName
).getFormattedMessage()
);
} else if (Strings.hasText(targetNodeName) == false && type == Type.REPLACE) {
throw new IllegalArgumentException("target node name is required for REPLACE type shutdowns");
}
this.targetNodeName = targetNodeName;
}
public SingleNodeShutdownMetadata(StreamInput in) throws IOException {
this.nodeId = in.readString();
this.type = in.readEnum(Type.class);
this.reason = in.readString();
this.startedAtMillis = in.readVLong();
this.nodeSeen = in.readBoolean();
this.allocationDelay = in.readOptionalTimeValue();
if (in.getVersion().onOrAfter(REPLACE_SHUTDOWN_TYPE_ADDED_VERSION)) {
this.targetNodeName = in.readOptionalString();
} else {
this.targetNodeName = null;
}
}
/**
* @return The ID of the node this {@link SingleNodeShutdownMetadata} concerns.
*/
public String getNodeId() {
return nodeId;
}
/**
* @return The type of shutdown this is (shutdown vs. permanent).
*/
public Type getType() {
return type;
}
/**
* @return The user-supplied reason this node is shutting down.
*/
public String getReason() {
return reason;
}
/**
* @return The timestamp that this shutdown procedure was started.
*/
public long getStartedAtMillis() {
return startedAtMillis;
}
/**
* @return A boolean indicated whether this node has been seen in the cluster since the shutdown was registered.
*/
public boolean getNodeSeen() {
return nodeSeen;
}
/**
* @return The name of the node to be used as a replacement for this node, or null.
*/
public String getTargetNodeName() {
return targetNodeName;
}
/**
* @return The amount of time shard reallocation should be delayed for shards on this node, so that they will not be automatically
* reassigned while the node is restarting. Will be {@code null} for non-restart shutdowns.
*/
@Nullable
public TimeValue getAllocationDelay() {
if (allocationDelay != null) {
return allocationDelay;
} else if (Type.RESTART.equals(type)) {
return DEFAULT_RESTART_SHARD_ALLOCATION_DELAY;
}
return null;
}
@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(nodeId);
if (out.getVersion().before(REPLACE_SHUTDOWN_TYPE_ADDED_VERSION) && this.type == SingleNodeShutdownMetadata.Type.REPLACE) {
out.writeEnum(SingleNodeShutdownMetadata.Type.REMOVE);
} else {
out.writeEnum(type);
}
out.writeString(reason);
out.writeVLong(startedAtMillis);
out.writeBoolean(nodeSeen);
out.writeOptionalTimeValue(allocationDelay);
if (out.getVersion().onOrAfter(REPLACE_SHUTDOWN_TYPE_ADDED_VERSION)) {
out.writeOptionalString(targetNodeName);
}
}
@Override
public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException {
builder.startObject();
{
builder.field(NODE_ID_FIELD.getPreferredName(), nodeId);
builder.field(TYPE_FIELD.getPreferredName(), type);
builder.field(REASON_FIELD.getPreferredName(), reason);
builder.timeField(STARTED_AT_MILLIS_FIELD.getPreferredName(), STARTED_AT_READABLE_FIELD, startedAtMillis);
builder.field(NODE_SEEN_FIELD.getPreferredName(), nodeSeen);
if (allocationDelay != null) {
builder.field(ALLOCATION_DELAY_FIELD.getPreferredName(), allocationDelay.getStringRep());
}
if (targetNodeName != null) {
builder.field(TARGET_NODE_NAME_FIELD.getPreferredName(), targetNodeName);
}
}
builder.endObject();
return builder;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if ((o instanceof SingleNodeShutdownMetadata) == false) return false;
SingleNodeShutdownMetadata that = (SingleNodeShutdownMetadata) o;
return getStartedAtMillis() == that.getStartedAtMillis()
&& getNodeId().equals(that.getNodeId())
&& getType() == that.getType()
&& getReason().equals(that.getReason())
&& getNodeSeen() == that.getNodeSeen()
&& Objects.equals(allocationDelay, that.allocationDelay)
&& Objects.equals(targetNodeName, that.targetNodeName);
}
@Override
public int hashCode() {
return Objects.hash(getNodeId(), getType(), getReason(), getStartedAtMillis(), getNodeSeen(), allocationDelay, targetNodeName);
}
@Override
public String toString() {
final StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("{")
.append("nodeId=[")
.append(nodeId)
.append(']')
.append(", type=[")
.append(type)
.append("], reason=[")
.append(reason)
.append(']');
if (allocationDelay != null) {
stringBuilder.append(", allocationDelay=[").append(allocationDelay).append("]");
}
stringBuilder.append("}");
return stringBuilder.toString();
}
public static Builder builder() {
return new Builder();
}
public static Builder builder(SingleNodeShutdownMetadata original) {
if (original == null) {
return builder();
}
return new Builder().setNodeId(original.getNodeId())
.setType(original.getType())
.setReason(original.getReason())
.setStartedAtMillis(original.getStartedAtMillis())
.setNodeSeen(original.getNodeSeen())
.setTargetNodeName(original.getTargetNodeName());
}
public static class Builder {
private String nodeId;
private Type type;
private String reason;
private long startedAtMillis = -1;
private boolean nodeSeen = false;
private TimeValue allocationDelay;
private String targetNodeName;
private Builder() {}
/**
* @param nodeId The node ID this metadata refers to.
* @return This builder.
*/
public Builder setNodeId(String nodeId) {
this.nodeId = nodeId;
return this;
}
/**
* @param type The type of shutdown.
* @return This builder.
*/
public Builder setType(Type type) {
this.type = type;
return this;
}
/**
* @param reason The reason for the shutdown. An arbitrary string provided by the user.
* @return This builder.
*/
public Builder setReason(String reason) {
this.reason = reason;
return this;
}
/**
* @param startedAtMillis The timestamp at which this shutdown was requested.
* @return This builder.
*/
public Builder setStartedAtMillis(long startedAtMillis) {
this.startedAtMillis = startedAtMillis;
return this;
}
/**
* @param nodeSeen Whether or not the node has been seen since the shutdown was registered.
* @return This builder.
*/
public Builder setNodeSeen(boolean nodeSeen) {
this.nodeSeen = nodeSeen;
return this;
}
/**
* @param allocationDelay The amount of time shard reallocation should be delayed while this node is offline.
* @return This builder.
*/
public Builder setAllocationDelay(TimeValue allocationDelay) {
this.allocationDelay = allocationDelay;
return this;
}
/**
* @param targetNodeName The name of the node which should be used to replcae this one. Only valid if the shutdown type is REPLACE.
* @return This builder.
*/
public Builder setTargetNodeName(String targetNodeName) {
this.targetNodeName = targetNodeName;
return this;
}
public SingleNodeShutdownMetadata build() {
if (startedAtMillis == -1) {
throw new IllegalArgumentException("start timestamp must be set");
}
return new SingleNodeShutdownMetadata(nodeId, type, reason, startedAtMillis, nodeSeen, allocationDelay, targetNodeName);
}
}
/**
* Describes the type of node shutdown - permanent (REMOVE) or temporary (RESTART).
*/
public enum Type {
REMOVE,
RESTART,
REPLACE;
public static Type parse(String type) {
if ("remove".equals(type.toLowerCase(Locale.ROOT))) {
return REMOVE;
} else if ("restart".equals(type.toLowerCase(Locale.ROOT))) {
return RESTART;
} else if ("replace".equals(type.toLowerCase(Locale.ROOT))) {
return REPLACE;
} else {
throw new IllegalArgumentException("unknown shutdown type: " + type);
}
}
}
/**
* Describes the status of a component of shutdown.
*/
public enum Status {
// These are ordered (see #combine(...))
NOT_STARTED,
IN_PROGRESS,
STALLED,
COMPLETE;
/**
* Merges multiple statuses into a single, final, status
*
* For example, if called with NOT_STARTED, IN_PROGRESS, and STALLED, the returned state is STALLED.
* Called with IN_PROGRESS, IN_PROGRESS, NOT_STARTED, the returned state is IN_PROGRESS.
* Called with IN_PROGRESS, NOT_STARTED, COMPLETE, the returned state is IN_PROGRESS
* Called with COMPLETE, COMPLETE, COMPLETE, the returned state is COMPLETE
* Called with an empty array, the returned state is COMPLETE
*/
public static Status combine(Status... statuses) {
int statusOrd = -1;
for (Status status : statuses) {
// Max the status up to, but not including, "complete"
if (status != COMPLETE) {
statusOrd = Math.max(status.ordinal(), statusOrd);
}
}
if (statusOrd == -1) {
// Either all the statuses were complete, or there were no statuses given
return COMPLETE;
} else {
return Status.values()[statusOrd];
}
}
}
}