Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ public static NodeShutdownComponentStatus parse(XContentParser parser) {
return PARSER.apply(parser, null);
}

public NodeShutdownComponentStatus() {
this(SingleNodeShutdownMetadata.Status.NOT_STARTED, null, null);
}

public NodeShutdownComponentStatus(
SingleNodeShutdownMetadata.Status status,
@Nullable Long startedAtMillis,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import java.io.IOException;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -81,13 +82,34 @@ public void writeTo(StreamOutput out) throws IOException {
}

/**
* Retrieve the data about nodes which are currently in the process of shutting down.
* @return A map of node IDs to information about the node's shutdown status.
* @return A map of NodeID to shutdown metadata.
*/
public Map<String, SingleNodeShutdownMetadata> getPerNodeInfo() {
public Map<String, SingleNodeShutdownMetadata> getAllNodeMetdataMap() {
return Collections.unmodifiableMap(nodes);
}

/**
* Add or update the shutdown metadata for a single node.
* @param nodeShutdownMetadata The single node shutdown metadata to add or update.
* @return A new {@link NodesShutdownMetadata} that reflects the updated value.
*/
public NodesShutdownMetadata putSingleNodeMetadata(SingleNodeShutdownMetadata nodeShutdownMetadata) {
HashMap<String, SingleNodeShutdownMetadata> newNodes = new HashMap<>(nodes);
newNodes.put(nodeShutdownMetadata.getNodeId(), nodeShutdownMetadata);
return new NodesShutdownMetadata(newNodes);
}

/**
* Removes all shutdown metadata for a particular node ID.
* @param nodeId The node ID to remove shutdown metadata for.
* @return A new {@link NodesShutdownMetadata} that does not contain shutdown metadata for the given node.
*/
public NodesShutdownMetadata removeSingleNodeMetadata(String nodeId) {
HashMap<String, SingleNodeShutdownMetadata> newNodes = new HashMap<>(nodes);
newNodes.remove(nodeId);
return new NodesShutdownMetadata(newNodes);
}

@Override
public Diff<Metadata.Custom> diff(Metadata.Custom previousState) {
return new NodeShutdownMetadataDiff((NodesShutdownMetadata) previousState, this);
Expand All @@ -113,12 +135,12 @@ public boolean equals(Object o) {
if (this == o) return true;
if ((o instanceof NodesShutdownMetadata) == false) return false;
NodesShutdownMetadata that = (NodesShutdownMetadata) o;
return getPerNodeInfo().equals(that.getPerNodeInfo());
return nodes.equals(that.nodes);
}

@Override
public int hashCode() {
return Objects.hash(getPerNodeInfo());
return Objects.hash(nodes);
}

@Override
Expand Down Expand Up @@ -150,7 +172,7 @@ public NodeShutdownMetadataDiff(StreamInput in) throws IOException {
@Override
public Metadata.Custom apply(Metadata.Custom part) {
TreeMap<String, SingleNodeShutdownMetadata> newNodes = new TreeMap<>(
nodesDiff.apply(((NodesShutdownMetadata) part).getPerNodeInfo())
nodesDiff.apply(((NodesShutdownMetadata) part).nodes)
);
return new NodesShutdownMetadata(newNodes);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,26 @@ public static SingleNodeShutdownMetadata parse(XContentParser parser) {
private final NodeShutdownComponentStatus persistentTasksStatus;
private final NodeShutdownComponentStatus pluginsStatus;


public SingleNodeShutdownMetadata(
/**
* @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 status The overall status of this shutdown.
* @param startedAtMillis The timestamp at which this shutdown was requested.
* @param shardMigrationStatus The status of shard migrations away from this node.
* @param persistentTasksStatus The status of persistent task migration away from this node.
* @param pluginsStatus The status of plugin shutdown on this node.
*/
private SingleNodeShutdownMetadata(
String nodeId,
Type type,
String reason,
Status status,
long startedAtMillis,
NodeShutdownComponentStatus shardMigrationStatus,
NodeShutdownComponentStatus persistentTasksStatus, NodeShutdownComponentStatus pluginsStatus) {
NodeShutdownComponentStatus persistentTasksStatus,
NodeShutdownComponentStatus pluginsStatus
) {
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");
Expand Down Expand Up @@ -143,7 +154,7 @@ public String getReason() {
/**
* @return The status of this node's shutdown.
*/
public Status isStatus() {
public Status getStatus() {
return status;
}

Expand All @@ -154,6 +165,27 @@ public long getStartedAtMillis() {
return startedAtMillis;
}

/**
* @return The status of shard migrations off of this node.
*/
public NodeShutdownComponentStatus getShardMigrationStatus() {
return shardMigrationStatus;
}

/**
* @return The status of persistent task shutdown on this node.
*/
public NodeShutdownComponentStatus getPersistentTasksStatus() {
return persistentTasksStatus;
}

/**
* @return The status of plugin shutdown on this node.
*/
public NodeShutdownComponentStatus getPluginsStatus() {
return pluginsStatus;
}

@Override
public void writeTo(StreamOutput out) throws IOException {
out.writeString(nodeId);
Expand Down Expand Up @@ -213,11 +245,137 @@ public int hashCode() {
);
}

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())
.setStatus(original.getStatus())
.setStartedAtMillis(original.getStartedAtMillis())
.setShardMigrationStatus(original.getShardMigrationStatus())
.setPersistentTasksStatus(original.getPersistentTasksStatus())
.setPluginsStatus(original.getPluginsStatus());
}

public static class Builder {
private String nodeId;
private Type type;
private String reason;
private long startedAtMillis = -1;
private Status status = Status.IN_PROGRESS;
private NodeShutdownComponentStatus shardMigrationStatus = new NodeShutdownComponentStatus();
private NodeShutdownComponentStatus persistentTasksStatus = new NodeShutdownComponentStatus();
private NodeShutdownComponentStatus pluginsStatus = new NodeShutdownComponentStatus();

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 status The status of this shutdown.
* @return This builder.
*/
public Builder setStatus(Status status) {
this.status = status;
return this;
}

/**
* @param shardMigrationStatus An object describing the status of shard migration away from this node.
* @return This builder.
*/
public Builder setShardMigrationStatus(NodeShutdownComponentStatus shardMigrationStatus) {
this.shardMigrationStatus = shardMigrationStatus;
return this;
}

/**
* @param persistentTasksStatus An object describing the status of persistent task migration away from this node.
* @return This builder.
*/
public Builder setPersistentTasksStatus(NodeShutdownComponentStatus persistentTasksStatus) {
this.persistentTasksStatus = persistentTasksStatus;
return this;
}

/**
* @param pluginsStatus An object describing the status of plugin shutdown on this node.
* @return
*/
public Builder setPluginsStatus(NodeShutdownComponentStatus pluginsStatus) {
this.pluginsStatus = pluginsStatus;
return this;
}

public SingleNodeShutdownMetadata build() {
if (startedAtMillis == -1) {
throw new IllegalArgumentException("start timestamp must be set");
}
return new SingleNodeShutdownMetadata(
nodeId,
type,
reason,
status,
startedAtMillis,
shardMigrationStatus,
persistentTasksStatus,
pluginsStatus
);
}
}

/**
* Describes the type of node shutdown - permanent (REMOVE) or temporary (RESTART).
*/
public enum Type {
REMOVE,
RESTART
}

/**
* Describes the status of a component of shutdown.
*/
public enum Status {
NOT_STARTED,
IN_PROGRESS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,47 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;

public class NodesShutdownMetadataTests extends AbstractDiffableSerializationTestCase<Metadata.Custom> {

public void testInsertNewNodeShutdownMetadata() {
NodesShutdownMetadata nodesShutdownMetadata = new NodesShutdownMetadata(new HashMap<>());
SingleNodeShutdownMetadata newNodeMetadata = randomNodeShutdownInfo();

nodesShutdownMetadata = nodesShutdownMetadata.putSingleNodeMetadata(newNodeMetadata);

assertThat(nodesShutdownMetadata.getAllNodeMetdataMap().get(newNodeMetadata.getNodeId()), equalTo(newNodeMetadata));
assertThat(nodesShutdownMetadata.getAllNodeMetdataMap().values(), contains(newNodeMetadata));
}

public void testRemoveShutdownMetadata() {
NodesShutdownMetadata nodesShutdownMetadata = new NodesShutdownMetadata(new HashMap<>());
List<SingleNodeShutdownMetadata> nodes = randomList(1, 20, this::randomNodeShutdownInfo);

for (SingleNodeShutdownMetadata node : nodes) {
nodesShutdownMetadata = nodesShutdownMetadata.putSingleNodeMetadata(node);
}

SingleNodeShutdownMetadata nodeToRemove = randomFrom(nodes);
nodesShutdownMetadata = nodesShutdownMetadata.removeSingleNodeMetadata(nodeToRemove.getNodeId());

assertThat(nodesShutdownMetadata.getAllNodeMetdataMap().get(nodeToRemove.getNodeId()), nullValue());
assertThat(nodesShutdownMetadata.getAllNodeMetdataMap().values(), hasSize(nodes.size() - 1));
assertThat(nodesShutdownMetadata.getAllNodeMetdataMap().values(), not(hasItem(nodeToRemove)));
}

@Override
protected Writeable.Reader<Diff<Metadata.Custom>> diffReader() {
return NodesShutdownMetadata.NodeShutdownMetadataDiff::new;
Expand All @@ -45,15 +80,15 @@ protected NodesShutdownMetadata createTestInstance() {
}

private SingleNodeShutdownMetadata randomNodeShutdownInfo() {
return new SingleNodeShutdownMetadata(
randomAlphaOfLength(5),
randomBoolean() ? SingleNodeShutdownMetadata.Type.REMOVE : SingleNodeShutdownMetadata.Type.RESTART,
randomAlphaOfLength(5),
randomStatus(),
randomNonNegativeLong(),
randomComponentStatus(),
randomComponentStatus(),
randomComponentStatus());
return SingleNodeShutdownMetadata.builder().setNodeId(randomAlphaOfLength(5))
.setType(randomBoolean() ? SingleNodeShutdownMetadata.Type.REMOVE : SingleNodeShutdownMetadata.Type.RESTART)
.setReason(randomAlphaOfLength(5))
.setStatus(randomStatus())
.setStartedAtMillis(randomNonNegativeLong())
.setShardMigrationStatus(randomComponentStatus())
.setPersistentTasksStatus(randomComponentStatus())
.setPluginsStatus(randomComponentStatus())
.build();
}

private SingleNodeShutdownMetadata.Status randomStatus() {
Expand Down
Empty file.
20 changes: 20 additions & 0 deletions x-pack/plugin/shutdown/qa/multi-node/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
apply plugin: 'elasticsearch.java-rest-test'

dependencies {
javaRestTestImplementation(testArtifact(project(xpackModule('core'))))
}

def clusterCredentials = [username: System.getProperty('tests.rest.cluster.username', 'test_admin'),
password: System.getProperty('tests.rest.cluster.password', 'x-pack-test-password')]

tasks.named("javaRestTest").configure {
systemProperty 'tests.rest.cluster.username', clusterCredentials.username
systemProperty 'tests.rest.cluster.password', clusterCredentials.password
}

testClusters.all {
testDistribution = 'DEFAULT'
numberOfNodes = 4

systemProperty 'es.shutdown_feature_flag_enabled', 'true'
}
Loading