-
Notifications
You must be signed in to change notification settings - Fork 25k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add noop detection to node shutdown actions (#85914)
When node shutdown apis are called to add or remove a node to be shutdown, it is possible the given node is already shutting down. In this case, there is no need to submit a cluster state update. This commit detects when this no-op case occurs and simply returns. relates #84847
- Loading branch information
Showing
5 changed files
with
176 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
67 changes: 67 additions & 0 deletions
67
...rc/test/java/org/elasticsearch/xpack/shutdown/TransportDeleteShutdownNodeActionTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
/* | ||
* 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; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
package org.elasticsearch.xpack.shutdown; | ||
|
||
import org.elasticsearch.action.ActionListener; | ||
import org.elasticsearch.action.support.ActionFilters; | ||
import org.elasticsearch.cluster.ClusterState; | ||
import org.elasticsearch.cluster.ClusterStateUpdateTask; | ||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; | ||
import org.elasticsearch.cluster.metadata.Metadata; | ||
import org.elasticsearch.cluster.metadata.NodesShutdownMetadata; | ||
import org.elasticsearch.cluster.metadata.SingleNodeShutdownMetadata; | ||
import org.elasticsearch.cluster.service.ClusterService; | ||
import org.elasticsearch.test.ESTestCase; | ||
import org.elasticsearch.threadpool.ThreadPool; | ||
import org.elasticsearch.transport.TransportService; | ||
import org.junit.Before; | ||
import org.mockito.ArgumentCaptor; | ||
|
||
import java.util.Map; | ||
|
||
import static org.elasticsearch.cluster.metadata.NodesShutdownMetadata.TYPE; | ||
import static org.hamcrest.Matchers.sameInstance; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.verify; | ||
|
||
public class TransportDeleteShutdownNodeActionTests extends ESTestCase { | ||
private ClusterService clusterService; | ||
private TransportDeleteShutdownNodeAction action; | ||
|
||
@Before | ||
public void init() { | ||
// TODO: it takes almost 2 seconds to create these mocks....WHY?!? | ||
var threadPool = mock(ThreadPool.class); | ||
var transportService = mock(TransportService.class); | ||
clusterService = mock(ClusterService.class); | ||
var actionFilters = mock(ActionFilters.class); | ||
var indexNameExpressionResolver = mock(IndexNameExpressionResolver.class); | ||
action = new TransportDeleteShutdownNodeAction( | ||
transportService, | ||
clusterService, | ||
threadPool, | ||
actionFilters, | ||
indexNameExpressionResolver | ||
); | ||
} | ||
|
||
public void testNoop() throws Exception { | ||
var singleNodeMetadata = mock(SingleNodeShutdownMetadata.class); | ||
var nodesShutdownMetadata = new NodesShutdownMetadata(Map.of("node1", singleNodeMetadata)); | ||
var metadata = Metadata.builder().putCustom(TYPE, nodesShutdownMetadata).build(); | ||
var clusterStateWithShutdown = ClusterState.builder(ClusterState.EMPTY_STATE).metadata(metadata).build(); | ||
|
||
var request = new DeleteShutdownNodeAction.Request("node1"); | ||
action.masterOperation(null, request, clusterStateWithShutdown, ActionListener.noop()); | ||
var updateTaskCapture = ArgumentCaptor.forClass(ClusterStateUpdateTask.class); | ||
verify(clusterService).submitStateUpdateTask(any(), updateTaskCapture.capture(), any()); | ||
ClusterState gotState = updateTaskCapture.getValue().execute(ClusterState.EMPTY_STATE); | ||
assertThat(gotState, sameInstance(ClusterState.EMPTY_STATE)); | ||
} | ||
} |
74 changes: 74 additions & 0 deletions
74
...n/src/test/java/org/elasticsearch/xpack/shutdown/TransportPutShutdownNodeActionTests.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
/* | ||
* 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; you may not use this file except in compliance with the Elastic License | ||
* 2.0. | ||
*/ | ||
|
||
package org.elasticsearch.xpack.shutdown; | ||
|
||
import org.elasticsearch.action.ActionListener; | ||
import org.elasticsearch.action.support.ActionFilters; | ||
import org.elasticsearch.cluster.ClusterState; | ||
import org.elasticsearch.cluster.ClusterStateUpdateTask; | ||
import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; | ||
import org.elasticsearch.cluster.metadata.SingleNodeShutdownMetadata.Type; | ||
import org.elasticsearch.cluster.service.ClusterService; | ||
import org.elasticsearch.core.TimeValue; | ||
import org.elasticsearch.test.ESTestCase; | ||
import org.elasticsearch.threadpool.ThreadPool; | ||
import org.elasticsearch.transport.TransportService; | ||
import org.junit.Before; | ||
import org.mockito.ArgumentCaptor; | ||
|
||
import static org.hamcrest.Matchers.sameInstance; | ||
import static org.mockito.ArgumentMatchers.any; | ||
import static org.mockito.Mockito.clearInvocations; | ||
import static org.mockito.Mockito.mock; | ||
import static org.mockito.Mockito.verify; | ||
import static org.mockito.Mockito.verifyNoInteractions; | ||
|
||
public class TransportPutShutdownNodeActionTests extends ESTestCase { | ||
|
||
private ClusterService clusterService; | ||
private TransportPutShutdownNodeAction action; | ||
|
||
@Before | ||
public void init() { | ||
// TODO: it takes almost 2 seconds to create these mocks....WHY?!? | ||
var threadPool = mock(ThreadPool.class); | ||
var transportService = mock(TransportService.class); | ||
clusterService = mock(ClusterService.class); | ||
var actionFilters = mock(ActionFilters.class); | ||
var indexNameExpressionResolver = mock(IndexNameExpressionResolver.class); | ||
action = new TransportPutShutdownNodeAction( | ||
transportService, | ||
clusterService, | ||
threadPool, | ||
actionFilters, | ||
indexNameExpressionResolver | ||
); | ||
} | ||
|
||
public void testNoop() throws Exception { | ||
var type = randomFrom(Type.REMOVE, Type.REPLACE, Type.RESTART); | ||
var allocationDelay = type == Type.RESTART ? TimeValue.timeValueMinutes(randomIntBetween(1, 3)) : null; | ||
var targetNodeName = type == Type.REPLACE ? randomAlphaOfLength(5) : null; | ||
var request = new PutShutdownNodeAction.Request("node1", type, "sunsetting", allocationDelay, targetNodeName); | ||
action.masterOperation(null, request, ClusterState.EMPTY_STATE, ActionListener.noop()); | ||
var updateTaskCapture = ArgumentCaptor.forClass(ClusterStateUpdateTask.class); | ||
verify(clusterService).submitStateUpdateTask(any(), updateTaskCapture.capture(), any()); | ||
ClusterState stableState = updateTaskCapture.getValue().execute(ClusterState.EMPTY_STATE); | ||
|
||
// run the request again, there should be no call to submit an update task | ||
clearInvocations(clusterService); | ||
action.masterOperation(null, request, stableState, ActionListener.noop()); | ||
verifyNoInteractions(clusterService); | ||
|
||
// run the request again with empty state, the update task should return the same state | ||
action.masterOperation(null, request, ClusterState.EMPTY_STATE, ActionListener.noop()); | ||
verify(clusterService).submitStateUpdateTask(any(), updateTaskCapture.capture(), any()); | ||
ClusterState gotState = updateTaskCapture.getValue().execute(stableState); | ||
assertThat(gotState, sameInstance(stableState)); | ||
} | ||
} |