Skip to content

Commit b445b6d

Browse files
Add cancellation test
1 parent a9caddf commit b445b6d

File tree

1 file changed

+181
-1
lines changed

1 file changed

+181
-1
lines changed

x-pack/plugin/profiler/src/internalClusterTest/java/org/elasticsearch/xpack/profiler/GetProfilingActionIT.java

Lines changed: 181 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,34 +7,81 @@
77

88
package org.elasticsearch.xpack.profiler;
99

10+
import org.apache.http.entity.ContentType;
11+
import org.apache.http.entity.StringEntity;
12+
import org.apache.logging.log4j.LogManager;
13+
import org.apache.lucene.util.SetOnce;
14+
import org.elasticsearch.action.admin.cluster.node.info.NodeInfo;
15+
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoResponse;
16+
import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksResponse;
1017
import org.elasticsearch.action.index.IndexResponse;
18+
import org.elasticsearch.action.support.PlainActionFuture;
19+
import org.elasticsearch.client.Cancellable;
20+
import org.elasticsearch.client.Request;
21+
import org.elasticsearch.client.Response;
22+
import org.elasticsearch.common.network.NetworkModule;
1123
import org.elasticsearch.common.settings.Settings;
1224
import org.elasticsearch.plugins.Plugin;
25+
import org.elasticsearch.plugins.PluginsService;
1326
import org.elasticsearch.rest.RestStatus;
27+
import org.elasticsearch.script.MockScriptPlugin;
28+
import org.elasticsearch.search.lookup.LeafStoredFieldsLookup;
29+
import org.elasticsearch.tasks.CancellableTask;
30+
import org.elasticsearch.tasks.Task;
31+
import org.elasticsearch.tasks.TaskId;
32+
import org.elasticsearch.tasks.TaskInfo;
33+
import org.elasticsearch.tasks.TaskManager;
1434
import org.elasticsearch.test.ESIntegTestCase;
35+
import org.elasticsearch.transport.TransportService;
36+
import org.elasticsearch.transport.netty4.Netty4Plugin;
1537
import org.elasticsearch.xcontent.XContentType;
1638
import org.junit.Before;
1739

1840
import java.io.IOException;
41+
import java.nio.charset.StandardCharsets;
42+
import java.util.ArrayList;
1943
import java.util.Collection;
44+
import java.util.Collections;
45+
import java.util.HashMap;
2046
import java.util.List;
2147
import java.util.Map;
48+
import java.util.concurrent.CancellationException;
49+
import java.util.concurrent.TimeUnit;
50+
import java.util.concurrent.atomic.AtomicBoolean;
51+
import java.util.concurrent.atomic.AtomicInteger;
52+
import java.util.function.Function;
53+
54+
import static org.elasticsearch.action.support.ActionTestUtils.wrapAsRestResponseListener;
55+
import static org.hamcrest.Matchers.greaterThan;
56+
import static org.hamcrest.Matchers.instanceOf;
2257

2358
@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST, numDataNodes = 1)
2459
public class GetProfilingActionIT extends ESIntegTestCase {
2560
@Override
2661
protected Collection<Class<? extends Plugin>> nodePlugins() {
27-
return List.of(ProfilingPlugin.class);
62+
return List.of(ProfilingPlugin.class, ScriptedBlockPlugin.class, getTestTransportPlugin());
2863
}
2964

3065
@Override
3166
protected Settings nodeSettings(int nodeOrdinal, Settings otherSettings) {
3267
return Settings.builder()
3368
.put(super.nodeSettings(nodeOrdinal, otherSettings))
3469
.put(ProfilingPlugin.PROFILING_ENABLED.getKey(), true)
70+
.put(NetworkModule.TRANSPORT_TYPE_KEY, Netty4Plugin.NETTY_TRANSPORT_NAME)
71+
.put(NetworkModule.HTTP_TYPE_KEY, Netty4Plugin.NETTY_HTTP_TRANSPORT_NAME)
3572
.build();
3673
}
3774

75+
@Override
76+
protected boolean addMockHttpTransport() {
77+
return false; // enable http
78+
}
79+
80+
@Override
81+
protected boolean ignoreExternalCluster() {
82+
return true;
83+
}
84+
3885
private byte[] read(String resource) throws IOException {
3986
return GetProfilingAction.class.getClassLoader().getResourceAsStream(resource).readAllBytes();
4087
}
@@ -104,4 +151,137 @@ public void testGetProfilingDataUnfiltered() throws Exception {
104151
assertNotNull(response.getExecutables());
105152
assertNotNull("libc.so.6", response.getExecutables().get("QCCDqjSg3bMK1C4YRK6Tiw"));
106153
}
154+
155+
public void testAutomaticCancellation() throws Exception {
156+
Request restRequest = new Request("POST", "/_profiling/stacktraces");
157+
restRequest.setEntity(new StringEntity("""
158+
{
159+
"sample_size": 10000,
160+
"query": {
161+
"bool": {
162+
"filter": [
163+
{
164+
"script": {
165+
"script": {
166+
"lang": "mockscript",
167+
"source": "search_block",
168+
"params": {}
169+
}
170+
}
171+
}
172+
]
173+
}
174+
}
175+
}
176+
""", ContentType.APPLICATION_JSON.withCharset(StandardCharsets.UTF_8)));
177+
verifyCancellation(GetProfilingAction.NAME, restRequest);
178+
}
179+
180+
void verifyCancellation(String action, Request restRequest) throws Exception {
181+
Map<String, String> nodeIdToName = readNodesInfo();
182+
List<ScriptedBlockPlugin> plugins = initBlockFactory();
183+
184+
PlainActionFuture<Response> future = PlainActionFuture.newFuture();
185+
Cancellable cancellable = getRestClient().performRequestAsync(restRequest, wrapAsRestResponseListener(future));
186+
187+
awaitForBlock(plugins);
188+
cancellable.cancel();
189+
ensureTaskIsCancelled(action, nodeIdToName::get);
190+
191+
disableBlocks(plugins);
192+
expectThrows(CancellationException.class, future::actionGet);
193+
}
194+
195+
private static Map<String, String> readNodesInfo() {
196+
Map<String, String> nodeIdToName = new HashMap<>();
197+
NodesInfoResponse nodesInfoResponse = client().admin().cluster().prepareNodesInfo().get();
198+
assertFalse(nodesInfoResponse.hasFailures());
199+
for (NodeInfo node : nodesInfoResponse.getNodes()) {
200+
nodeIdToName.put(node.getNode().getId(), node.getNode().getName());
201+
}
202+
return nodeIdToName;
203+
}
204+
205+
private static void ensureTaskIsCancelled(String transportAction, Function<String, String> nodeIdToName) throws Exception {
206+
SetOnce<TaskInfo> searchTask = new SetOnce<>();
207+
ListTasksResponse listTasksResponse = client().admin().cluster().prepareListTasks().get();
208+
for (TaskInfo task : listTasksResponse.getTasks()) {
209+
if (task.action().equals(transportAction)) {
210+
searchTask.set(task);
211+
}
212+
}
213+
assertNotNull(searchTask.get());
214+
TaskId taskId = searchTask.get().taskId();
215+
String nodeName = nodeIdToName.apply(taskId.getNodeId());
216+
assertBusy(() -> {
217+
TaskManager taskManager = internalCluster().getInstance(TransportService.class, nodeName).getTaskManager();
218+
Task task = taskManager.getTask(taskId.getId());
219+
assertThat(task, instanceOf(CancellableTask.class));
220+
assertTrue(((CancellableTask) task).isCancelled());
221+
});
222+
}
223+
224+
private static List<ScriptedBlockPlugin> initBlockFactory() {
225+
List<ScriptedBlockPlugin> plugins = new ArrayList<>();
226+
for (PluginsService pluginsService : internalCluster().getDataNodeInstances(PluginsService.class)) {
227+
plugins.addAll(pluginsService.filterPlugins(ScriptedBlockPlugin.class));
228+
}
229+
for (ScriptedBlockPlugin plugin : plugins) {
230+
plugin.reset();
231+
plugin.enableBlock();
232+
}
233+
return plugins;
234+
}
235+
236+
private void awaitForBlock(List<ScriptedBlockPlugin> plugins) throws Exception {
237+
assertBusy(() -> {
238+
int numberOfBlockedPlugins = 0;
239+
for (ScriptedBlockPlugin plugin : plugins) {
240+
numberOfBlockedPlugins += plugin.hits.get();
241+
}
242+
logger.info("The plugin blocked on {} shards", numberOfBlockedPlugins);
243+
assertThat(numberOfBlockedPlugins, greaterThan(0));
244+
}, 10, TimeUnit.SECONDS);
245+
}
246+
247+
private static void disableBlocks(List<ScriptedBlockPlugin> plugins) {
248+
for (ScriptedBlockPlugin plugin : plugins) {
249+
plugin.disableBlock();
250+
}
251+
}
252+
253+
public static class ScriptedBlockPlugin extends MockScriptPlugin {
254+
static final String SCRIPT_NAME = "search_block";
255+
256+
private final AtomicInteger hits = new AtomicInteger();
257+
258+
private final AtomicBoolean shouldBlock = new AtomicBoolean(true);
259+
260+
void reset() {
261+
hits.set(0);
262+
}
263+
264+
void disableBlock() {
265+
shouldBlock.set(false);
266+
}
267+
268+
void enableBlock() {
269+
shouldBlock.set(true);
270+
}
271+
272+
@Override
273+
public Map<String, Function<Map<String, Object>, Object>> pluginScripts() {
274+
return Collections.singletonMap(SCRIPT_NAME, params -> {
275+
LeafStoredFieldsLookup fieldsLookup = (LeafStoredFieldsLookup) params.get("_fields");
276+
LogManager.getLogger(GetProfilingActionIT.class).info("Blocking on the document {}", fieldsLookup.get("_id"));
277+
hits.incrementAndGet();
278+
try {
279+
waitUntil(() -> shouldBlock.get() == false);
280+
} catch (Exception e) {
281+
throw new RuntimeException(e);
282+
}
283+
return true;
284+
});
285+
}
286+
}
107287
}

0 commit comments

Comments
 (0)