diff --git a/server/src/internalClusterTest/java/org/elasticsearch/action/admin/cluster/tasks/ListTasksIT.java b/server/src/internalClusterTest/java/org/elasticsearch/action/admin/cluster/tasks/ListTasksIT.java new file mode 100644 index 0000000000000..4cb821e2104d7 --- /dev/null +++ b/server/src/internalClusterTest/java/org/elasticsearch/action/admin/cluster/tasks/ListTasksIT.java @@ -0,0 +1,46 @@ +/* + * 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.action.admin.cluster.tasks; + +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.test.ESIntegTestCase; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.empty; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; + +public class ListTasksIT extends ESIntegTestCase { + + public void testListTasksFilteredByDescription() { + + // The list tasks action itself is filtered out via this description filter + assertThat( + client().admin().cluster().prepareListTasks().setDetailed(true).setDescriptions("match_nothing*").get().getTasks(), + is(empty()) + ); + + // The list tasks action itself is kept via this description filter which matches everything + assertThat( + client().admin().cluster().prepareListTasks().setDetailed(true).setDescriptions("*").get().getTasks(), + is(not(empty())) + ); + + } + + public void testListTasksValidation() { + + ActionRequestValidationException ex = expectThrows( + ActionRequestValidationException.class, + () -> client().admin().cluster().prepareListTasks().setDescriptions("*").get() + ); + assertThat(ex.getMessage(), containsString("matching on descriptions is not available when [detailed] is false")); + + } +} diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/tasks/list/ListTasksRequest.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/tasks/list/ListTasksRequest.java index fdda251cc86f2..37dcf3b41ca67 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/tasks/list/ListTasksRequest.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/tasks/list/ListTasksRequest.java @@ -8,20 +8,32 @@ package org.elasticsearch.action.admin.cluster.node.tasks.list; +import org.elasticsearch.Version; +import org.elasticsearch.action.ActionRequestValidationException; import org.elasticsearch.action.support.tasks.BaseTasksRequest; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.tasks.Task; import java.io.IOException; +import static org.elasticsearch.common.regex.Regex.simpleMatch; +import static org.elasticsearch.action.ValidateActions.addValidationError; +import static org.elasticsearch.common.util.CollectionUtils.isEmpty; + /** * A request to get node tasks */ public class ListTasksRequest extends BaseTasksRequest { + public static final String[] ANY_DESCRIPTION = Strings.EMPTY_ARRAY; + private boolean detailed = false; private boolean waitForCompletion = false; + private String[] descriptions = ANY_DESCRIPTION; + public ListTasksRequest() { } @@ -29,6 +41,9 @@ public ListTasksRequest(StreamInput in) throws IOException { super(in); detailed = in.readBoolean(); waitForCompletion = in.readBoolean(); + if (in.getVersion().onOrAfter(Version.V_8_0_0)) { + descriptions = in.readStringArray(); + } } @Override @@ -36,6 +51,25 @@ public void writeTo(StreamOutput out) throws IOException { super.writeTo(out); out.writeBoolean(detailed); out.writeBoolean(waitForCompletion); + if (out.getVersion().onOrAfter(Version.V_8_0_0)) { + out.writeStringArray(descriptions); + } + } + + @Override + public ActionRequestValidationException validate() { + ActionRequestValidationException validationException = super.validate(); + if (descriptions.length > 0 && detailed == false) { + validationException = addValidationError("matching on descriptions is not available when [detailed] is false", + validationException); + } + return validationException; + } + + @Override + public boolean match(Task task) { + return super.match(task) + && (isEmpty(getDescriptions()) || simpleMatch(getDescriptions(), task.getDescription())); } /** @@ -68,4 +102,21 @@ public ListTasksRequest setWaitForCompletion(boolean waitForCompletion) { return this; } + /** + * Description patters on which to match. + * + * If other matching criteria are set, descriptions are matched last once other criteria are satisfied + * + * Matching on descriptions is only available if `detailed` is `true`. + * @return array of absolute or simple wildcard matching strings + */ + public String[] getDescriptions() { + return descriptions; + } + + public ListTasksRequest setDescriptions(String... descriptions) { + this.descriptions = descriptions; + return this; + } + } diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/tasks/list/ListTasksRequestBuilder.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/tasks/list/ListTasksRequestBuilder.java index 1ffaf4f123fad..c8dd737592ef2 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/node/tasks/list/ListTasksRequestBuilder.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/node/tasks/list/ListTasksRequestBuilder.java @@ -35,4 +35,16 @@ public final ListTasksRequestBuilder setWaitForCompletion(boolean waitForComplet request.setWaitForCompletion(waitForCompletion); return this; } + + /** + * Should the task match specific descriptions. + * + * Cannot be used unless {@link ListTasksRequestBuilder#setDetailed(boolean)} is `true` + * @param descriptions string array containing simple regex or absolute description matching + * @return the builder with descriptions set + */ + public final ListTasksRequestBuilder setDescriptions(String... descriptions) { + request.setDescriptions(descriptions); + return this; + } } diff --git a/server/src/test/java/org/elasticsearch/action/admin/cluster/node/tasks/list/ListTasksRequestTests.java b/server/src/test/java/org/elasticsearch/action/admin/cluster/node/tasks/list/ListTasksRequestTests.java new file mode 100644 index 0000000000000..9239d8a2250f3 --- /dev/null +++ b/server/src/test/java/org/elasticsearch/action/admin/cluster/node/tasks/list/ListTasksRequestTests.java @@ -0,0 +1,63 @@ +/* + * 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.action.admin.cluster.node.tasks.list; + +import org.elasticsearch.action.ActionRequestValidationException; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.tasks.TaskId; +import org.elasticsearch.test.ESTestCase; + +import java.util.Collections; + +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.nullValue; + +public class ListTasksRequestTests extends ESTestCase { + + public void testValidation() { + ActionRequestValidationException ex = new ListTasksRequest().setDescriptions("foo*").validate(); + assertThat(ex, is(not(nullValue()))); + + ex = new ListTasksRequest().setDescriptions("foo*").setDetailed(true).validate(); + assertThat(ex, is(nullValue())); + } + + public void testMatch() { + ListTasksRequest filterOnDescription = new ListTasksRequest().setDescriptions("foo*", "*bar", "absolute").setActions("my_action*"); + + assertTrue(filterOnDescription.match(taskWithActionDescription("my_action_foo", "foo_action"))); + assertTrue(filterOnDescription.match(taskWithActionDescription("my_action_bar", "absolute"))); + assertTrue(filterOnDescription.match(taskWithActionDescription("my_action_baz", "action_bar"))); + + assertFalse(filterOnDescription.match(taskWithActionDescription("my_action_foo", "not_wanted"))); + assertFalse(filterOnDescription.match(taskWithActionDescription("not_wanted_action", "foo_action"))); + + + ListTasksRequest notFilterOnDescription = new ListTasksRequest().setActions("my_action*"); + assertTrue(notFilterOnDescription.match(taskWithActionDescription("my_action_foo", "foo_action"))); + assertTrue(notFilterOnDescription.match(taskWithActionDescription("my_action_bar", "absolute"))); + assertTrue(notFilterOnDescription.match(taskWithActionDescription("my_action_baz", "action_bar"))); + assertTrue(notFilterOnDescription.match(taskWithActionDescription("my_action_baz", randomAlphaOfLength(10)))); + + assertFalse(notFilterOnDescription.match(taskWithActionDescription("not_wanted_action", randomAlphaOfLength(10)))); + } + + private static Task taskWithActionDescription(String action, String description) { + return new Task( + randomNonNegativeLong(), + randomAlphaOfLength(10), + action, + description, + new TaskId("test_node", randomNonNegativeLong()), + Collections.emptyMap() + ); + } + +}