Skip to content

Commit ac18c76

Browse files
committed
Integrate Node Shutdown API with cluster metadata (elastic#71162)
This commit hooks up the Node Shutdown API to the Node Shutdown cluster metadata, so using the API will result in the appropriate writes to the cluster state.
1 parent 234a7c0 commit ac18c76

File tree

16 files changed

+646
-41
lines changed

16 files changed

+646
-41
lines changed

server/src/main/java/org/elasticsearch/cluster/metadata/NodeShutdownComponentStatus.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ public static NodeShutdownComponentStatus parse(XContentParser parser) {
4646
return PARSER.apply(parser, null);
4747
}
4848

49+
public NodeShutdownComponentStatus() {
50+
this(SingleNodeShutdownMetadata.Status.NOT_STARTED, null, null);
51+
}
52+
4953
public NodeShutdownComponentStatus(
5054
SingleNodeShutdownMetadata.Status status,
5155
@Nullable Long startedAtMillis,

server/src/main/java/org/elasticsearch/cluster/metadata/NodesShutdownMetadata.java

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import java.io.IOException;
2424
import java.util.Collections;
2525
import java.util.EnumSet;
26+
import java.util.HashMap;
2627
import java.util.List;
2728
import java.util.Map;
2829
import java.util.Objects;
@@ -81,13 +82,34 @@ public void writeTo(StreamOutput out) throws IOException {
8182
}
8283

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

91+
/**
92+
* Add or update the shutdown metadata for a single node.
93+
* @param nodeShutdownMetadata The single node shutdown metadata to add or update.
94+
* @return A new {@link NodesShutdownMetadata} that reflects the updated value.
95+
*/
96+
public NodesShutdownMetadata putSingleNodeMetadata(SingleNodeShutdownMetadata nodeShutdownMetadata) {
97+
HashMap<String, SingleNodeShutdownMetadata> newNodes = new HashMap<>(nodes);
98+
newNodes.put(nodeShutdownMetadata.getNodeId(), nodeShutdownMetadata);
99+
return new NodesShutdownMetadata(newNodes);
100+
}
101+
102+
/**
103+
* Removes all shutdown metadata for a particular node ID.
104+
* @param nodeId The node ID to remove shutdown metadata for.
105+
* @return A new {@link NodesShutdownMetadata} that does not contain shutdown metadata for the given node.
106+
*/
107+
public NodesShutdownMetadata removeSingleNodeMetadata(String nodeId) {
108+
HashMap<String, SingleNodeShutdownMetadata> newNodes = new HashMap<>(nodes);
109+
newNodes.remove(nodeId);
110+
return new NodesShutdownMetadata(newNodes);
111+
}
112+
91113
@Override
92114
public Diff<Metadata.Custom> diff(Metadata.Custom previousState) {
93115
return new NodeShutdownMetadataDiff((NodesShutdownMetadata) previousState, this);
@@ -113,12 +135,12 @@ public boolean equals(Object o) {
113135
if (this == o) return true;
114136
if ((o instanceof NodesShutdownMetadata) == false) return false;
115137
NodesShutdownMetadata that = (NodesShutdownMetadata) o;
116-
return getPerNodeInfo().equals(that.getPerNodeInfo());
138+
return nodes.equals(that.nodes);
117139
}
118140

119141
@Override
120142
public int hashCode() {
121-
return Objects.hash(getPerNodeInfo());
143+
return Objects.hash(nodes);
122144
}
123145

124146
@Override
@@ -150,7 +172,7 @@ public NodeShutdownMetadataDiff(StreamInput in) throws IOException {
150172
@Override
151173
public Metadata.Custom apply(Metadata.Custom part) {
152174
TreeMap<String, SingleNodeShutdownMetadata> newNodes = new TreeMap<>(
153-
nodesDiff.apply(((NodesShutdownMetadata) part).getPerNodeInfo())
175+
nodesDiff.apply(((NodesShutdownMetadata) part).nodes)
154176
);
155177
return new NodesShutdownMetadata(newNodes);
156178
}

server/src/main/java/org/elasticsearch/cluster/metadata/SingleNodeShutdownMetadata.java

Lines changed: 162 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -89,15 +89,26 @@ public static SingleNodeShutdownMetadata parse(XContentParser parser) {
8989
private final NodeShutdownComponentStatus persistentTasksStatus;
9090
private final NodeShutdownComponentStatus pluginsStatus;
9191

92-
93-
public SingleNodeShutdownMetadata(
92+
/**
93+
* @param nodeId The node ID that this shutdown metadata refers to.
94+
* @param type The type of shutdown. See {@link Type}.
95+
* @param reason The reason for the shutdown, per the original shutdown request.
96+
* @param status The overall status of this shutdown.
97+
* @param startedAtMillis The timestamp at which this shutdown was requested.
98+
* @param shardMigrationStatus The status of shard migrations away from this node.
99+
* @param persistentTasksStatus The status of persistent task migration away from this node.
100+
* @param pluginsStatus The status of plugin shutdown on this node.
101+
*/
102+
private SingleNodeShutdownMetadata(
94103
String nodeId,
95104
Type type,
96105
String reason,
97106
Status status,
98107
long startedAtMillis,
99108
NodeShutdownComponentStatus shardMigrationStatus,
100-
NodeShutdownComponentStatus persistentTasksStatus, NodeShutdownComponentStatus pluginsStatus) {
109+
NodeShutdownComponentStatus persistentTasksStatus,
110+
NodeShutdownComponentStatus pluginsStatus
111+
) {
101112
this.nodeId = Objects.requireNonNull(nodeId, "node ID must not be null");
102113
this.type = Objects.requireNonNull(type, "shutdown type must not be null");
103114
this.reason = Objects.requireNonNull(reason, "shutdown reason must not be null");
@@ -143,7 +154,7 @@ public String getReason() {
143154
/**
144155
* @return The status of this node's shutdown.
145156
*/
146-
public Status isStatus() {
157+
public Status getStatus() {
147158
return status;
148159
}
149160

@@ -154,6 +165,27 @@ public long getStartedAtMillis() {
154165
return startedAtMillis;
155166
}
156167

168+
/**
169+
* @return The status of shard migrations off of this node.
170+
*/
171+
public NodeShutdownComponentStatus getShardMigrationStatus() {
172+
return shardMigrationStatus;
173+
}
174+
175+
/**
176+
* @return The status of persistent task shutdown on this node.
177+
*/
178+
public NodeShutdownComponentStatus getPersistentTasksStatus() {
179+
return persistentTasksStatus;
180+
}
181+
182+
/**
183+
* @return The status of plugin shutdown on this node.
184+
*/
185+
public NodeShutdownComponentStatus getPluginsStatus() {
186+
return pluginsStatus;
187+
}
188+
157189
@Override
158190
public void writeTo(StreamOutput out) throws IOException {
159191
out.writeString(nodeId);
@@ -213,11 +245,137 @@ public int hashCode() {
213245
);
214246
}
215247

248+
public static Builder builder() {
249+
return new Builder();
250+
}
251+
252+
public static Builder builder(SingleNodeShutdownMetadata original) {
253+
if (original == null) {
254+
return builder();
255+
}
256+
return new Builder()
257+
.setNodeId(original.getNodeId())
258+
.setType(original.getType())
259+
.setReason(original.getReason())
260+
.setStatus(original.getStatus())
261+
.setStartedAtMillis(original.getStartedAtMillis())
262+
.setShardMigrationStatus(original.getShardMigrationStatus())
263+
.setPersistentTasksStatus(original.getPersistentTasksStatus())
264+
.setPluginsStatus(original.getPluginsStatus());
265+
}
266+
267+
public static class Builder {
268+
private String nodeId;
269+
private Type type;
270+
private String reason;
271+
private long startedAtMillis = -1;
272+
private Status status = Status.IN_PROGRESS;
273+
private NodeShutdownComponentStatus shardMigrationStatus = new NodeShutdownComponentStatus();
274+
private NodeShutdownComponentStatus persistentTasksStatus = new NodeShutdownComponentStatus();
275+
private NodeShutdownComponentStatus pluginsStatus = new NodeShutdownComponentStatus();
276+
277+
private Builder() {}
278+
279+
/**
280+
* @param nodeId The node ID this metadata refers to.
281+
* @return This builder.
282+
*/
283+
public Builder setNodeId(String nodeId) {
284+
this.nodeId = nodeId;
285+
return this;
286+
}
287+
288+
/**
289+
* @param type The type of shutdown.
290+
* @return This builder.
291+
*/
292+
public Builder setType(Type type) {
293+
this.type = type;
294+
return this;
295+
}
296+
297+
/**
298+
* @param reason The reason for the shutdown. An arbitrary string provided by the user.
299+
* @return This builder.
300+
*/
301+
public Builder setReason(String reason) {
302+
this.reason = reason;
303+
return this;
304+
}
305+
306+
/**
307+
* @param startedAtMillis The timestamp at which this shutdown was requested.
308+
* @return This builder.
309+
*/
310+
public Builder setStartedAtMillis(long startedAtMillis) {
311+
this.startedAtMillis = startedAtMillis;
312+
return this;
313+
}
314+
315+
/**
316+
* @param status The status of this shutdown.
317+
* @return This builder.
318+
*/
319+
public Builder setStatus(Status status) {
320+
this.status = status;
321+
return this;
322+
}
323+
324+
/**
325+
* @param shardMigrationStatus An object describing the status of shard migration away from this node.
326+
* @return This builder.
327+
*/
328+
public Builder setShardMigrationStatus(NodeShutdownComponentStatus shardMigrationStatus) {
329+
this.shardMigrationStatus = shardMigrationStatus;
330+
return this;
331+
}
332+
333+
/**
334+
* @param persistentTasksStatus An object describing the status of persistent task migration away from this node.
335+
* @return This builder.
336+
*/
337+
public Builder setPersistentTasksStatus(NodeShutdownComponentStatus persistentTasksStatus) {
338+
this.persistentTasksStatus = persistentTasksStatus;
339+
return this;
340+
}
341+
342+
/**
343+
* @param pluginsStatus An object describing the status of plugin shutdown on this node.
344+
* @return
345+
*/
346+
public Builder setPluginsStatus(NodeShutdownComponentStatus pluginsStatus) {
347+
this.pluginsStatus = pluginsStatus;
348+
return this;
349+
}
350+
351+
public SingleNodeShutdownMetadata build() {
352+
if (startedAtMillis == -1) {
353+
throw new IllegalArgumentException("start timestamp must be set");
354+
}
355+
return new SingleNodeShutdownMetadata(
356+
nodeId,
357+
type,
358+
reason,
359+
status,
360+
startedAtMillis,
361+
shardMigrationStatus,
362+
persistentTasksStatus,
363+
pluginsStatus
364+
);
365+
}
366+
}
367+
368+
/**
369+
* Describes the type of node shutdown - permanent (REMOVE) or temporary (RESTART).
370+
*/
216371
public enum Type {
217372
REMOVE,
218373
RESTART
219374
}
220375

376+
/**
377+
* Describes the status of a component of shutdown.
378+
*/
221379
public enum Status {
222380
NOT_STARTED,
223381
IN_PROGRESS,

server/src/test/java/org/elasticsearch/cluster/metadata/NodesShutdownMetadataTests.java

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,47 @@
1616
import java.io.IOException;
1717
import java.util.ArrayList;
1818
import java.util.EnumSet;
19+
import java.util.HashMap;
20+
import java.util.List;
1921
import java.util.Map;
2022
import java.util.function.Function;
2123
import java.util.stream.Collectors;
2224

25+
import static org.hamcrest.Matchers.contains;
26+
import static org.hamcrest.Matchers.equalTo;
27+
import static org.hamcrest.Matchers.hasItem;
28+
import static org.hamcrest.Matchers.hasSize;
29+
import static org.hamcrest.Matchers.not;
30+
import static org.hamcrest.Matchers.nullValue;
31+
2332
public class NodesShutdownMetadataTests extends AbstractDiffableSerializationTestCase<Metadata.Custom> {
2433

34+
public void testInsertNewNodeShutdownMetadata() {
35+
NodesShutdownMetadata nodesShutdownMetadata = new NodesShutdownMetadata(new HashMap<>());
36+
SingleNodeShutdownMetadata newNodeMetadata = randomNodeShutdownInfo();
37+
38+
nodesShutdownMetadata = nodesShutdownMetadata.putSingleNodeMetadata(newNodeMetadata);
39+
40+
assertThat(nodesShutdownMetadata.getAllNodeMetdataMap().get(newNodeMetadata.getNodeId()), equalTo(newNodeMetadata));
41+
assertThat(nodesShutdownMetadata.getAllNodeMetdataMap().values(), contains(newNodeMetadata));
42+
}
43+
44+
public void testRemoveShutdownMetadata() {
45+
NodesShutdownMetadata nodesShutdownMetadata = new NodesShutdownMetadata(new HashMap<>());
46+
List<SingleNodeShutdownMetadata> nodes = randomList(1, 20, this::randomNodeShutdownInfo);
47+
48+
for (SingleNodeShutdownMetadata node : nodes) {
49+
nodesShutdownMetadata = nodesShutdownMetadata.putSingleNodeMetadata(node);
50+
}
51+
52+
SingleNodeShutdownMetadata nodeToRemove = randomFrom(nodes);
53+
nodesShutdownMetadata = nodesShutdownMetadata.removeSingleNodeMetadata(nodeToRemove.getNodeId());
54+
55+
assertThat(nodesShutdownMetadata.getAllNodeMetdataMap().get(nodeToRemove.getNodeId()), nullValue());
56+
assertThat(nodesShutdownMetadata.getAllNodeMetdataMap().values(), hasSize(nodes.size() - 1));
57+
assertThat(nodesShutdownMetadata.getAllNodeMetdataMap().values(), not(hasItem(nodeToRemove)));
58+
}
59+
2560
@Override
2661
protected Writeable.Reader<Diff<Metadata.Custom>> diffReader() {
2762
return NodesShutdownMetadata.NodeShutdownMetadataDiff::new;
@@ -45,15 +80,15 @@ protected NodesShutdownMetadata createTestInstance() {
4580
}
4681

4782
private SingleNodeShutdownMetadata randomNodeShutdownInfo() {
48-
return new SingleNodeShutdownMetadata(
49-
randomAlphaOfLength(5),
50-
randomBoolean() ? SingleNodeShutdownMetadata.Type.REMOVE : SingleNodeShutdownMetadata.Type.RESTART,
51-
randomAlphaOfLength(5),
52-
randomStatus(),
53-
randomNonNegativeLong(),
54-
randomComponentStatus(),
55-
randomComponentStatus(),
56-
randomComponentStatus());
83+
return SingleNodeShutdownMetadata.builder().setNodeId(randomAlphaOfLength(5))
84+
.setType(randomBoolean() ? SingleNodeShutdownMetadata.Type.REMOVE : SingleNodeShutdownMetadata.Type.RESTART)
85+
.setReason(randomAlphaOfLength(5))
86+
.setStatus(randomStatus())
87+
.setStartedAtMillis(randomNonNegativeLong())
88+
.setShardMigrationStatus(randomComponentStatus())
89+
.setPersistentTasksStatus(randomComponentStatus())
90+
.setPluginsStatus(randomComponentStatus())
91+
.build();
5792
}
5893

5994
private SingleNodeShutdownMetadata.Status randomStatus() {

x-pack/plugin/shutdown/qa/build.gradle

Whitespace-only changes.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
apply plugin: 'elasticsearch.java-rest-test'
2+
3+
dependencies {
4+
javaRestTestImplementation(testArtifact(project(xpackModule('core'))))
5+
}
6+
7+
def clusterCredentials = [username: System.getProperty('tests.rest.cluster.username', 'test_admin'),
8+
password: System.getProperty('tests.rest.cluster.password', 'x-pack-test-password')]
9+
10+
tasks.named("javaRestTest").configure {
11+
systemProperty 'tests.rest.cluster.username', clusterCredentials.username
12+
systemProperty 'tests.rest.cluster.password', clusterCredentials.password
13+
}
14+
15+
testClusters.all {
16+
testDistribution = 'DEFAULT'
17+
numberOfNodes = 4
18+
19+
systemProperty 'es.shutdown_feature_flag_enabled', 'true'
20+
}

0 commit comments

Comments
 (0)