diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java index ceb87009b6291..0b356007822a1 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/RequestConverters.java @@ -53,6 +53,7 @@ import org.elasticsearch.client.core.TermVectorsRequest; import org.elasticsearch.client.indices.AnalyzeRequest; import org.elasticsearch.client.security.RefreshPolicy; +import org.elasticsearch.client.tasks.TaskId; import org.elasticsearch.cluster.health.ClusterHealthStatus; import org.elasticsearch.common.Nullable; import org.elasticsearch.common.Priority; @@ -80,13 +81,13 @@ import org.elasticsearch.script.mustache.MultiSearchTemplateRequest; import org.elasticsearch.script.mustache.SearchTemplateRequest; import org.elasticsearch.search.fetch.subphase.FetchSourceContext; -import org.elasticsearch.tasks.TaskId; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -1029,19 +1030,41 @@ Params withWaitForCompletion(Boolean waitForCompletion) { } Params withNodes(String[] nodes) { - if (nodes != null && nodes.length > 0) { + return withNodes(Arrays.asList(nodes)); + } + + Params withNodes(List nodes) { + if (nodes != null && nodes.size() > 0) { return putParam("nodes", String.join(",", nodes)); } return this; } Params withActions(String[] actions) { - if (actions != null && actions.length > 0) { + return withActions(Arrays.asList(actions)); + } + + Params withActions(List actions) { + if (actions != null && actions.size() > 0) { return putParam("actions", String.join(",", actions)); } return this; } + Params withTaskId(org.elasticsearch.tasks.TaskId taskId) { + if (taskId != null && taskId.isSet()) { + return putParam("task_id", taskId.toString()); + } + return this; + } + + Params withParentTaskId(org.elasticsearch.tasks.TaskId parentTaskId) { + if (parentTaskId != null && parentTaskId.isSet()) { + return putParam("parent_task_id", parentTaskId.toString()); + } + return this; + } + Params withTaskId(TaskId taskId) { if (taskId != null && taskId.isSet()) { return putParam("task_id", taskId.toString()); diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/TasksClient.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/TasksClient.java index 04ccd3239335f..7ed38ca2126e0 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/TasksClient.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/TasksClient.java @@ -20,10 +20,10 @@ package org.elasticsearch.client; import org.elasticsearch.action.ActionListener; -import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksRequest; -import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksResponse; import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksRequest; import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksResponse; +import org.elasticsearch.client.tasks.CancelTasksRequest; +import org.elasticsearch.client.tasks.CancelTasksResponse; import org.elasticsearch.client.tasks.GetTaskRequest; import org.elasticsearch.client.tasks.GetTaskResponse; diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/TasksRequestConverters.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/TasksRequestConverters.java index f30efabc823e3..9099e8a854121 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/TasksRequestConverters.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/TasksRequestConverters.java @@ -21,23 +21,24 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; -import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksRequest; import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksRequest; import org.elasticsearch.client.RequestConverters.EndpointBuilder; +import org.elasticsearch.client.tasks.CancelTasksRequest; import org.elasticsearch.client.tasks.GetTaskRequest; final class TasksRequestConverters { private TasksRequestConverters() {} - static Request cancelTasks(CancelTasksRequest cancelTasksRequest) { + static Request cancelTasks(CancelTasksRequest req) { Request request = new Request(HttpPost.METHOD_NAME, "/_tasks/_cancel"); RequestConverters.Params params = new RequestConverters.Params(); - params.withTimeout(cancelTasksRequest.getTimeout()) - .withTaskId(cancelTasksRequest.getTaskId()) - .withNodes(cancelTasksRequest.getNodes()) - .withParentTaskId(cancelTasksRequest.getParentTaskId()) - .withActions(cancelTasksRequest.getActions()); + req.getTimeout().ifPresent(params::withTimeout); + req.getTaskId().ifPresent(params::withTaskId); + req.getParentTaskId().ifPresent(params::withParentTaskId); + params + .withNodes(req.getNodes()) + .withActions(req.getActions()); request.addParameters(params.asMap()); return request; } @@ -70,5 +71,5 @@ static Request getTask(GetTaskRequest getTaskRequest) { request.addParameters(params.asMap()); return request; } - + } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/CancelTasksRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/CancelTasksRequest.java new file mode 100644 index 0000000000000..9677c72928195 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/CancelTasksRequest.java @@ -0,0 +1,151 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.tasks; + +import org.elasticsearch.client.Validatable; +import org.elasticsearch.common.unit.TimeValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; + +public class CancelTasksRequest implements Validatable { + + private final List nodes = new ArrayList<>(); + private final List actions = new ArrayList<>(); + private Optional timeout = Optional.empty(); + private Optional parentTaskId = Optional.empty(); + private Optional taskId = Optional.empty(); + + CancelTasksRequest(){} + + void setNodes(List nodes) { + this.nodes.addAll(nodes); + } + + public List getNodes() { + return nodes; + } + + void setTimeout(TimeValue timeout) { + this.timeout = Optional.of(timeout); + } + + public Optional getTimeout() { + return timeout; + } + + void setActions(List actions) { + this.actions.addAll(actions); + } + + public List getActions() { + return actions; + } + + void setParentTaskId(TaskId parentTaskId) { + this.parentTaskId = Optional.of(parentTaskId); + } + + public Optional getParentTaskId() { + return parentTaskId; + } + + void setTaskId(TaskId taskId) { + this.taskId = Optional.of(taskId); + } + + public Optional getTaskId() { + return taskId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof CancelTasksRequest)) return false; + CancelTasksRequest that = (CancelTasksRequest) o; + return Objects.equals(getNodes(), that.getNodes()) && + Objects.equals(getActions(), that.getActions()) && + Objects.equals(getTimeout(), that.getTimeout()) && + Objects.equals(getParentTaskId(), that.getParentTaskId()) && + Objects.equals(getTaskId(), that.getTaskId()) ; + } + + @Override + public int hashCode() { + return Objects.hash(getNodes(), getActions(), getTimeout(), getParentTaskId(), getTaskId()); + } + + @Override + public String toString() { + return "CancelTasksRequest{" + + "nodes=" + nodes + + ", actions=" + actions + + ", timeout=" + timeout + + ", parentTaskId=" + parentTaskId + + ", taskId=" + taskId + + '}'; + } + + public static class Builder { + private Optional timeout = Optional.empty(); + private Optional taskId = Optional.empty(); + private Optional parentTaskId = Optional.empty(); + private List actionsFilter = new ArrayList<>(); + private List nodesFilter = new ArrayList<>(); + + public Builder withTimeout(TimeValue timeout){ + this.timeout = Optional.of(timeout); + return this; + } + + public Builder withTaskId(TaskId taskId){ + this.taskId = Optional.of(taskId); + return this; + } + + public Builder withParentTaskId(TaskId taskId){ + this.parentTaskId = Optional.of(taskId); + return this; + } + + public Builder withActionsFiltered(List actions){ + this.actionsFilter.clear(); + this.actionsFilter.addAll(actions); + return this; + } + + public Builder withNodesFiltered(List nodes){ + this.nodesFilter.clear(); + this.nodesFilter.addAll(nodes); + return this; + } + + public CancelTasksRequest build() { + CancelTasksRequest request = new CancelTasksRequest(); + timeout.ifPresent(request::setTimeout); + taskId.ifPresent(request::setTaskId); + parentTaskId.ifPresent(request::setParentTaskId); + request.setNodes(nodesFilter); + request.setActions(actionsFilter); + return request; + } + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/CancelTasksResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/CancelTasksResponse.java new file mode 100644 index 0000000000000..200f67da1ee13 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/CancelTasksResponse.java @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.tasks; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.io.IOException; +import java.util.List; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.optionalConstructorArg; + +/** + * cancel tasks response that contains + * - task failures + * - node failures + * - tasks + */ +public class CancelTasksResponse extends ListTasksResponse { + + CancelTasksResponse(List nodesInfoData, + List taskFailures, + List nodeFailures) { + super(nodesInfoData, taskFailures, nodeFailures); + } + + public static CancelTasksResponse fromXContent(final XContentParser parser) throws IOException { + return PARSER.parse(parser, null); + } + + private static ConstructingObjectParser PARSER; + + static { + ConstructingObjectParser parser = new ConstructingObjectParser<>("cancel_tasks_response", true, + constructingObjects -> { + int i = 0; + @SuppressWarnings("unchecked") + List tasksFailures = (List) constructingObjects[i++]; + @SuppressWarnings("unchecked") + List nodeFailures = (List) constructingObjects[i++]; + @SuppressWarnings("unchecked") + List nodesInfoData = (List) constructingObjects[i]; + return new CancelTasksResponse(nodesInfoData, tasksFailures, nodeFailures); + }); + + parser.declareObjectArray(optionalConstructorArg(), (p, c) -> + TaskOperationFailure.fromXContent(p), new ParseField("task_failures")); + parser.declareObjectArray(optionalConstructorArg(), (p, c) -> + ElasticsearchException.fromXContent(p), new ParseField("node_failures")); + parser.declareNamedObjects(optionalConstructorArg(), NodeData.PARSER, new ParseField("nodes")); + PARSER = parser; + } + + @Override + public boolean equals(Object o) { + return super.equals(o); + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public String toString() { + return "CancelTasksResponse{" + + "taskFailures=" + taskFailures + + ", nodeFailures=" + nodeFailures + + ", nodesInfoData=" + nodesInfoData + + ", tasks=" + tasks + + ", taskGroups=" + taskGroups + + '}'; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/ElasticsearchException.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/ElasticsearchException.java new file mode 100644 index 0000000000000..7893e758c8dc8 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/ElasticsearchException.java @@ -0,0 +1,225 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.tasks; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.XContentParser; +import java.io.IOException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.XContentParserUtils.ensureExpectedToken; + +/** + * client side counterpart of server side + * {@link org.elasticsearch.ElasticsearchException} + * It wraps the same content but it is not throwable. + */ +public class ElasticsearchException { + + private static final String TYPE = "type"; + private static final String REASON = "reason"; + private static final String CAUSED_BY = "caused_by"; + private static final ParseField SUPPRESSED = new ParseField("suppressed"); + private static final String STACK_TRACE = "stack_trace"; + private static final String HEADER = "header"; + private static final String ROOT_CAUSE = "root_cause"; + + private String msg; + private ElasticsearchException cause; + private final Map> headers = new HashMap<>(); + private final List suppressed = new ArrayList<>(); + + ElasticsearchException(String msg) { + this.msg = msg; + this.cause = null; + } + + ElasticsearchException(String msg, ElasticsearchException cause) { + this.msg = msg; + this.cause = cause; + } + + public String getMsg() { + return msg; + } + + public ElasticsearchException getCause() { + return cause; + } + + public List getSuppressed() { + return suppressed; + } + + void addSuppressed(List suppressed){ + this.suppressed.addAll(suppressed); + } + + /** + * Generate a {@link ElasticsearchException} from a {@link XContentParser}. This does not + * return the original exception type (ie NodeClosedException for example) but just wraps + * the type, the reason and the cause of the exception. It also recursively parses the + * tree structure of the cause, returning it as a tree structure of {@link ElasticsearchException} + * instances. + */ + static ElasticsearchException fromXContent(XContentParser parser) throws IOException { + XContentParser.Token token = parser.nextToken(); + ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); + return innerFromXContent(parser, false); + } + + private static ElasticsearchException innerFromXContent(XContentParser parser, boolean parseRootCauses) throws IOException { + XContentParser.Token token = parser.currentToken(); + ensureExpectedToken(XContentParser.Token.FIELD_NAME, token, parser::getTokenLocation); + + String type = null, reason = null, stack = null; + ElasticsearchException cause = null; + Map> headers = new HashMap<>(); + List rootCauses = new ArrayList<>(); + List suppressed = new ArrayList<>(); + + for (; token == XContentParser.Token.FIELD_NAME; token = parser.nextToken()) { + String currentFieldName = parser.currentName(); + token = parser.nextToken(); + + if (token.isValue()) { + if (TYPE.equals(currentFieldName)) { + type = parser.text(); + } else if (REASON.equals(currentFieldName)) { + reason = parser.text(); + } else if (STACK_TRACE.equals(currentFieldName)) { + stack = parser.text(); + } + } else if (token == XContentParser.Token.START_OBJECT) { + if (CAUSED_BY.equals(currentFieldName)) { + cause = fromXContent(parser); + } else if (HEADER.equals(currentFieldName)) { + while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) { + if (token == XContentParser.Token.FIELD_NAME) { + currentFieldName = parser.currentName(); + } else { + List values = headers.getOrDefault(currentFieldName, new ArrayList<>()); + if (token == XContentParser.Token.VALUE_STRING) { + values.add(parser.text()); + } else if (token == XContentParser.Token.START_ARRAY) { + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + if (token == XContentParser.Token.VALUE_STRING) { + values.add(parser.text()); + } else { + parser.skipChildren(); + } + } + } else if (token == XContentParser.Token.START_OBJECT) { + parser.skipChildren(); + } + headers.put(currentFieldName, values); + } + } + } else { + // Any additional metadata object added by the metadataToXContent method is ignored + // and skipped, so that the parser does not fail on unknown fields. The parser only + // support metadata key-pairs and metadata arrays of values. + parser.skipChildren(); + } + } else if (token == XContentParser.Token.START_ARRAY) { + if (parseRootCauses && ROOT_CAUSE.equals(currentFieldName)) { + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + rootCauses.add(fromXContent(parser)); + } + } else if (SUPPRESSED.match(currentFieldName, parser.getDeprecationHandler())) { + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + suppressed.add(fromXContent(parser)); + } + } else { + // Parse the array and add each item to the corresponding list of metadata. + // Arrays of objects are not supported yet and just ignored and skipped. + List values = new ArrayList<>(); + while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) { + if (token == XContentParser.Token.VALUE_STRING) { + values.add(parser.text()); + } else { + parser.skipChildren(); + } + } + } + } + } + + ElasticsearchException e = new ElasticsearchException(buildMessage(type, reason, stack), cause); + for (Map.Entry> header : headers.entrySet()) { + e.addHeader(header.getKey(), header.getValue()); + } + + // Adds root causes as suppressed exception. This way they are not lost + // after parsing and can be retrieved using getSuppressed() method. + e.suppressed.addAll(rootCauses); + e.suppressed.addAll(suppressed); + + return e; + } + + void addHeader(String key, List value) { + headers.put(key,value); + + } + + public Map> getHeaders() { + return headers; + } + + static String buildMessage(String type, String reason, String stack) { + StringBuilder message = new StringBuilder("Elasticsearch exception ["); + message.append(TYPE).append('=').append(type).append(", "); + message.append(REASON).append('=').append(reason); + if (stack != null) { + message.append(", ").append(STACK_TRACE).append('=').append(stack); + } + message.append(']'); + return message.toString(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ElasticsearchException)) return false; + ElasticsearchException that = (ElasticsearchException) o; + return Objects.equals(getMsg(), that.getMsg()) && + Objects.equals(getCause(), that.getCause()) && + Objects.equals(getHeaders(), that.getHeaders()) && + Objects.equals(getSuppressed(), that.getSuppressed()); + } + + @Override + public int hashCode() { + return Objects.hash(getMsg(), getCause(), getHeaders(), getSuppressed()); + } + + @Override + public String toString() { + return "ElasticsearchException{" + + "msg='" + msg + '\'' + + ", cause=" + cause + + ", headers=" + headers + + ", suppressed=" + suppressed + + '}'; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/GetTaskRequest.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/GetTaskRequest.java index 0dc3168937573..1e49e82967792 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/GetTaskRequest.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/GetTaskRequest.java @@ -101,8 +101,8 @@ public boolean equals(Object obj) { } GetTaskRequest other = (GetTaskRequest) obj; return Objects.equals(nodeId, other.nodeId) && - taskId == other.taskId && - waitForCompletion == other.waitForCompletion && + taskId == other.taskId && + waitForCompletion == other.waitForCompletion && Objects.equals(timeout, other.timeout); } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/GetTaskResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/GetTaskResponse.java index 05d40f12f5b21..0bacd6ed0c514 100644 --- a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/GetTaskResponse.java +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/GetTaskResponse.java @@ -16,7 +16,6 @@ * specific language governing permissions and limitations * under the License. */ - package org.elasticsearch.client.tasks; import org.elasticsearch.common.ParseField; @@ -31,16 +30,16 @@ public class GetTaskResponse { private final TaskInfo taskInfo; public static final ParseField COMPLETED = new ParseField("completed"); public static final ParseField TASK = new ParseField("task"); - + public GetTaskResponse(boolean completed, TaskInfo taskInfo) { this.completed = completed; this.taskInfo = taskInfo; } - + public boolean isCompleted() { return completed; } - + public TaskInfo getTaskInfo() { return taskInfo; } @@ -50,9 +49,9 @@ public TaskInfo getTaskInfo() { static { PARSER.declareBoolean(constructorArg(), COMPLETED); PARSER.declareObject(constructorArg(), (p, c) -> TaskInfo.fromXContent(p), TASK); - } + } public static GetTaskResponse fromXContent(XContentParser parser) { return PARSER.apply(parser, null); - } + } } diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/ListTasksResponse.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/ListTasksResponse.java new file mode 100644 index 0000000000000..722e889d99a5d --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/ListTasksResponse.java @@ -0,0 +1,138 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.tasks; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.toList; + +public class ListTasksResponse { + + protected final List taskFailures = new ArrayList<>(); + protected final List nodeFailures = new ArrayList<>(); + protected final List nodesInfoData = new ArrayList<>(); + protected final List tasks = new ArrayList<>(); + protected final List taskGroups = new ArrayList<>(); + + ListTasksResponse(List nodesInfoData, + List taskFailures, + List nodeFailures) { + if (taskFailures != null) { + this.taskFailures.addAll(taskFailures); + } + if (nodeFailures != null) { + this.nodeFailures.addAll(nodeFailures); + } + if (nodesInfoData != null) { + this.nodesInfoData.addAll(nodesInfoData); + } + this.tasks.addAll(this + .nodesInfoData + .stream() + .flatMap(nodeData -> nodeData.getTasks().stream()) + .collect(toList()) + ); + this.taskGroups.addAll(buildTaskGroups()); + } + + private List buildTaskGroups() { + Map taskGroups = new HashMap<>(); + List topLevelTasks = new ArrayList<>(); + // First populate all tasks + for (TaskInfo taskInfo : this.tasks) { + taskGroups.put(taskInfo.getTaskId(), TaskGroup.builder(taskInfo)); + } + + // Now go through all task group builders and add children to their parents + for (TaskGroup.Builder taskGroup : taskGroups.values()) { + TaskId parentTaskId = taskGroup.getTaskInfo().getParentTaskId(); + if (parentTaskId != null) { + TaskGroup.Builder parentTask = taskGroups.get(parentTaskId); + if (parentTask != null) { + // we found parent in the list of tasks - add it to the parent list + parentTask.addGroup(taskGroup); + } else { + // we got zombie or the parent was filtered out - add it to the top task list + topLevelTasks.add(taskGroup); + } + } else { + // top level task - add it to the top task list + topLevelTasks.add(taskGroup); + } + } + return topLevelTasks.stream().map(TaskGroup.Builder::build).collect(Collectors.toUnmodifiableList()); + } + + public List getTasks() { + return tasks; + } + + public Map> getPerNodeTasks() { + return getTasks() + .stream() + .collect(groupingBy(TaskInfo::getNodeId)); + } + + public List getTaskFailures() { + return taskFailures; + } + + public List getNodeFailures() { + return nodeFailures; + } + + public List getTaskGroups() { + return taskGroups; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof ListTasksResponse)) return false; + ListTasksResponse response = (ListTasksResponse) o; + return nodesInfoData.equals(response.nodesInfoData) && + Objects.equals + (getTaskFailures(), response.getTaskFailures()) && + Objects.equals(getNodeFailures(), response.getNodeFailures()) && + Objects.equals(getTasks(), response.getTasks()) && + Objects.equals(getTaskGroups(), response.getTaskGroups()); + } + + @Override + public int hashCode() { + return Objects.hash(nodesInfoData, getTaskFailures(), getNodeFailures(), getTasks(), getTaskGroups()); + } + + @Override + public String toString() { + return "CancelTasksResponse{" + + "nodesInfoData=" + nodesInfoData + + ", taskFailures=" + taskFailures + + ", nodeFailures=" + nodeFailures + + ", tasks=" + tasks + + ", taskGroups=" + taskGroups + + '}'; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/NodeData.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/NodeData.java new file mode 100644 index 0000000000000..2a7388e0e595a --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/NodeData.java @@ -0,0 +1,160 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.tasks; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; + +class NodeData { + + private String nodeId; + private String name; + private String transportAddress; + private String host; + private String ip; + private final List roles = new ArrayList<>(); + private final Map attributes = new HashMap<>(); + private final List tasks = new ArrayList<>(); + + NodeData(String nodeId) { + this.nodeId = nodeId; + } + + void setName(String name) { + this.name = name; + } + + public void setAttributes(Map attributes) { + if(attributes!=null){ + this.attributes.putAll(attributes); + } + } + + void setTransportAddress(String transportAddress) { + this.transportAddress = transportAddress; + } + + void setHost(String host) { + this.host = host; + } + + void setIp(String ip) { + this.ip = ip; + } + + void setRoles(List roles) { + if(roles!=null){ + this.roles.addAll(roles); + } + } + + public String getNodeId() { + return nodeId; + } + + public String getName() { + return name; + } + + public String getTransportAddress() { + return transportAddress; + } + + public String getHost() { + return host; + } + + public String getIp() { + return ip; + } + + public List getRoles() { + return roles; + } + + public Map getAttributes() { + return attributes; + } + + public List getTasks() { + return tasks; + } + + void setTasks(List tasks) { + if(tasks!=null){ + this.tasks.addAll(tasks); + } + } + + @Override + public String toString() { + return "NodeData{" + + "nodeId='" + nodeId + '\'' + + ", name='" + name + '\'' + + ", transportAddress='" + transportAddress + '\'' + + ", host='" + host + '\'' + + ", ip='" + ip + '\'' + + ", roles=" + roles + + ", attributes=" + attributes + + '}'; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof NodeData)) return false; + NodeData nodeData = (NodeData) o; + return Objects.equals(getNodeId(), nodeData.getNodeId()) && + Objects.equals(getName(), nodeData.getName()) && + Objects.equals(getTransportAddress(), nodeData.getTransportAddress()) && + Objects.equals(getHost(), nodeData.getHost()) && + Objects.equals(getIp(), nodeData.getIp()) && + Objects.equals(getRoles(), nodeData.getRoles()) && + Objects.equals(getAttributes(), nodeData.getAttributes()) && + Objects.equals(getTasks(), nodeData.getTasks()); + } + + @Override + public int hashCode() { + return Objects.hash(getNodeId(), getName(), getTransportAddress(), getHost(), getIp(), getRoles(), getAttributes(), getTasks()); + } + + public static final ObjectParser.NamedObjectParser PARSER; + + static { + ObjectParser parser = new ObjectParser<>("nodes"); + parser.declareString(NodeData::setName, new ParseField("name")); + parser.declareString(NodeData::setTransportAddress, new ParseField("transport_address")); + parser.declareString(NodeData::setHost, new ParseField("host")); + parser.declareString(NodeData::setIp, new ParseField("ip")); + parser.declareStringArray(NodeData::setRoles, new ParseField("roles")); + parser.declareField(NodeData::setAttributes, + (p, c) -> p.mapStrings(), + new ParseField("attributes"), + ObjectParser.ValueType.OBJECT); + parser.declareNamedObjects(NodeData::setTasks, TaskInfo.PARSER, new ParseField("tasks")); + PARSER = (XContentParser p, Void v, String nodeId) -> parser.parse(p, new NodeData(nodeId), null); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/TaskGroup.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/TaskGroup.java new file mode 100644 index 0000000000000..6fce60a111140 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/TaskGroup.java @@ -0,0 +1,101 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.tasks; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Client side counterpart of server side version. + * + * {@link org.elasticsearch.action.admin.cluster.node.tasks.list.TaskGroup} + */ +public class TaskGroup { + + private final TaskInfo task; + + @Override + public String toString() { + return "TaskGroup{" + + "task=" + task + + ", childTasks=" + childTasks + + '}'; + } + + private final List childTasks = new ArrayList<>(); + + public TaskGroup(TaskInfo task, List childTasks) { + this.task = task; + this.childTasks.addAll(childTasks); + } + + public static TaskGroup.Builder builder(TaskInfo taskInfo) { + return new TaskGroup.Builder(taskInfo); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TaskGroup)) return false; + TaskGroup taskGroup = (TaskGroup) o; + return Objects.equals(task, taskGroup.task) && + Objects.equals(getChildTasks(), taskGroup.getChildTasks()); + } + + @Override + public int hashCode() { + return Objects.hash(task, getChildTasks()); + } + + public static class Builder { + private TaskInfo taskInfo; + private List childTasks; + + private Builder(TaskInfo taskInfo) { + this.taskInfo = taskInfo; + childTasks = new ArrayList<>(); + } + + public void addGroup(TaskGroup.Builder builder) { + childTasks.add(builder); + } + + public TaskInfo getTaskInfo() { + return taskInfo; + } + + public TaskGroup build() { + return new TaskGroup( + taskInfo, + childTasks.stream().map(TaskGroup.Builder::build).collect(Collectors.toList()) + ); + } + } + + public TaskInfo getTaskInfo() { + return task; + } + + public List getChildTasks() { + return childTasks; + } +} + diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/TaskId.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/TaskId.java new file mode 100644 index 0000000000000..c0dc16f92d475 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/TaskId.java @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.tasks; + +import java.util.Objects; + +/** + * client side version of a {@link org.elasticsearch.tasks.TaskId} + */ +public class TaskId { + + protected final String nodeId; + protected final long id; + + public TaskId(String nodeId, long id) { + this.nodeId = nodeId; + this.id = id; + } + + /** + * accepts a raw format task id + * @param taskId expected to be nodeid:taskId + */ + public TaskId(String taskId) { + if (taskId == null) { + throw new IllegalArgumentException("null task id"); + } + String[] s = taskId.split(":"); + if (s.length != 2) { + throw new IllegalArgumentException("malformed task id " + taskId); + } + this.nodeId = s[0]; + try { + this.id = Long.parseLong(s[1]); + } catch (NumberFormatException ex) { + throw new IllegalArgumentException("malformed task id " + taskId, ex); + } + } + + public String getNodeId() { + return nodeId; + } + + public long getId() { + return id; + } + + public boolean isSet() { + return id != -1L; + } + + @Override + public String toString() { + if (isSet()) { + return nodeId + ":" + id; + } else { + return "unset"; + } + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TaskId)) return false; + TaskId taskId = (TaskId) o; + return getId() == taskId.getId() && + Objects.equals(getNodeId(), taskId.getNodeId()); + } + + @Override + public int hashCode() { + return Objects.hash(getNodeId(), getId()); + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/TaskInfo.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/TaskInfo.java new file mode 100644 index 0000000000000..c02dc880b9663 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/TaskInfo.java @@ -0,0 +1,194 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.tasks; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** + * client side counterpart of server side + *

+ * {@link org.elasticsearch.tasks.TaskInfo} + */ +public class TaskInfo { + + private TaskId taskId; + private String type; + private String action; + private String description; + private long startTime; + private long runningTimeNanos; + private boolean cancellable; + private TaskId parentTaskId; + private final Map status = new HashMap<>(); + private final Map headers = new HashMap<>(); + + public TaskInfo(TaskId taskId) { + this.taskId = taskId; + } + + public TaskId getTaskId() { + return taskId; + } + + public String getNodeId() { + return taskId.nodeId; + } + + public String getType() { + return type; + } + + void setType(String type) { + this.type = type; + } + + public String getAction() { + return action; + } + + void setAction(String action) { + this.action = action; + } + + public String getDescription() { + return description; + } + + void setDescription(String description) { + this.description = description; + } + + public long getStartTime() { + return startTime; + } + + void setStartTime(long startTime) { + this.startTime = startTime; + } + + public long getRunningTimeNanos() { + return runningTimeNanos; + } + + void setRunningTimeNanos(long runningTimeNanos) { + this.runningTimeNanos = runningTimeNanos; + } + + public boolean isCancellable() { + return cancellable; + } + + void setCancellable(boolean cancellable) { + this.cancellable = cancellable; + } + + public TaskId getParentTaskId() { + return parentTaskId; + } + + void setParentTaskId(String parentTaskId) { + this.parentTaskId = new TaskId(parentTaskId); + } + + public Map getHeaders() { + return headers; + } + + void setHeaders(Map headers) { + this.headers.putAll(headers); + } + + void setStatus(Map status) { + this.status.putAll(status); + } + + public Map getStatus() { + return status; + } + + private void noOpParse(Object s) {} + + public static final ObjectParser.NamedObjectParser PARSER; + + static { + ObjectParser parser = new ObjectParser<>("tasks", true, null); + // already provided in constructor: triggering a no-op + parser.declareString(TaskInfo::noOpParse, new ParseField("node")); + // already provided in constructor: triggering a no-op + parser.declareLong(TaskInfo::noOpParse, new ParseField("id")); + parser.declareString(TaskInfo::setType, new ParseField("type")); + parser.declareString(TaskInfo::setAction, new ParseField("action")); + parser.declareObject(TaskInfo::setStatus, (p, c) -> p.map(), new ParseField("status")); + parser.declareString(TaskInfo::setDescription, new ParseField("description")); + parser.declareLong(TaskInfo::setStartTime, new ParseField("start_time_in_millis")); + parser.declareLong(TaskInfo::setRunningTimeNanos, new ParseField("running_time_in_nanos")); + parser.declareBoolean(TaskInfo::setCancellable, new ParseField("cancellable")); + parser.declareString(TaskInfo::setParentTaskId, new ParseField("parent_task_id")); + parser.declareObject(TaskInfo::setHeaders, (p, c) -> p.mapStrings(), new ParseField("headers")); + PARSER = (XContentParser p, Void v, String name) -> parser.parse(p, new TaskInfo(new TaskId(name)), null); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TaskInfo)) return false; + TaskInfo taskInfo = (TaskInfo) o; + return getStartTime() == taskInfo.getStartTime() && + getRunningTimeNanos() == taskInfo.getRunningTimeNanos() && + isCancellable() == taskInfo.isCancellable() && + Objects.equals(getTaskId(), taskInfo.getTaskId()) && + Objects.equals(getType(), taskInfo.getType()) && + Objects.equals(getAction(), taskInfo.getAction()) && + Objects.equals(getDescription(), taskInfo.getDescription()) && + Objects.equals(getParentTaskId(), taskInfo.getParentTaskId()) && + Objects.equals(status, taskInfo.status) && + Objects.equals(getHeaders(), taskInfo.getHeaders()); + } + + @Override + public int hashCode() { + return Objects.hash( + getTaskId(), getType(), getAction(), getDescription(), getStartTime(), + getRunningTimeNanos(), isCancellable(), getParentTaskId(), status, getHeaders() + ); + } + + + @Override + public String toString() { + return "TaskInfo{" + + "taskId=" + taskId + + ", type='" + type + '\'' + + ", action='" + action + '\'' + + ", description='" + description + '\'' + + ", startTime=" + startTime + + ", runningTimeNanos=" + runningTimeNanos + + ", cancellable=" + cancellable + + ", parentTaskId=" + parentTaskId + + ", status=" + status + + ", headers=" + headers + + '}'; + } +} diff --git a/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/TaskOperationFailure.java b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/TaskOperationFailure.java new file mode 100644 index 0000000000000..8aa222da04cb6 --- /dev/null +++ b/client/rest-high-level/src/main/java/org/elasticsearch/client/tasks/TaskOperationFailure.java @@ -0,0 +1,107 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.tasks; + +import org.elasticsearch.common.ParseField; +import org.elasticsearch.common.xcontent.ConstructingObjectParser; +import org.elasticsearch.common.xcontent.XContentParser; + +import java.util.Objects; + +import static org.elasticsearch.common.xcontent.ConstructingObjectParser.constructorArg; + +/** + * client side counterpart of server side + * {@link org.elasticsearch.action.TaskOperationFailure} + */ +public class TaskOperationFailure { + + private final String nodeId; + private final long taskId; + private final ElasticsearchException reason; + private final String status; + + public TaskOperationFailure(String nodeId, long taskId,String status, ElasticsearchException reason) { + this.nodeId = nodeId; + this.taskId = taskId; + this.status = status; + this.reason = reason; + } + + public String getNodeId() { + return nodeId; + } + + public long getTaskId() { + return taskId; + } + + public ElasticsearchException getReason() { + return reason; + } + + public String getStatus() { + return status; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof TaskOperationFailure)) return false; + TaskOperationFailure that = (TaskOperationFailure) o; + return getTaskId() == that.getTaskId() && + Objects.equals(getNodeId(), that.getNodeId()) && + Objects.equals(getReason(), that.getReason()) && + Objects.equals(getStatus(), that.getStatus()); + } + + @Override + public int hashCode() { + return Objects.hash(getNodeId(), getTaskId(), getReason(), getStatus()); + } + @Override + public String toString() { + return "TaskOperationFailure{" + + "nodeId='" + nodeId + '\'' + + ", taskId=" + taskId + + ", reason=" + reason + + ", status='" + status + '\'' + + '}'; + } + public static TaskOperationFailure fromXContent(XContentParser parser) { + return PARSER.apply(parser, null); + } + + private static final ConstructingObjectParser PARSER = + new ConstructingObjectParser<>("task_info", true, constructorObjects -> { + int i = 0; + String nodeId = (String) constructorObjects[i++]; + long taskId = (long) constructorObjects[i++]; + String status = (String) constructorObjects[i++]; + ElasticsearchException reason = (ElasticsearchException) constructorObjects[i]; + return new TaskOperationFailure(nodeId, taskId, status, reason); + }); + + static { + PARSER.declareString(constructorArg(), new ParseField("node_id")); + PARSER.declareLong(constructorArg(), new ParseField("task_id")); + PARSER.declareString(constructorArg(), new ParseField("status")); + PARSER.declareObject(constructorArg(), (parser, c) -> ElasticsearchException.fromXContent(parser), new ParseField("reason")); + } +} diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/TasksIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/TasksIT.java index 8f2b6cc1d1849..59183dbf0c0f2 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/TasksIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/TasksIT.java @@ -19,21 +19,20 @@ package org.elasticsearch.client; -import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksRequest; -import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksResponse; import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksRequest; import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksResponse; import org.elasticsearch.action.admin.cluster.node.tasks.list.TaskGroup; import org.elasticsearch.action.bulk.BulkRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.support.WriteRequest.RefreshPolicy; +import org.elasticsearch.client.tasks.CancelTasksRequest; +import org.elasticsearch.client.tasks.CancelTasksResponse; import org.elasticsearch.client.tasks.GetTaskRequest; import org.elasticsearch.client.tasks.GetTaskResponse; +import org.elasticsearch.client.tasks.TaskId; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentType; import org.elasticsearch.rest.RestStatus; -import org.elasticsearch.tasks.TaskId; -import org.elasticsearch.tasks.TaskInfo; import java.io.IOException; import java.util.Collections; @@ -58,12 +57,12 @@ public void testListTasks() throws IOException { assertThat(response.getTasks().size(), greaterThanOrEqualTo(2)); boolean listTasksFound = false; for (TaskGroup taskGroup : response.getTaskGroups()) { - TaskInfo parent = taskGroup.getTaskInfo(); + org.elasticsearch.tasks.TaskInfo parent = taskGroup.getTaskInfo(); if ("cluster:monitor/tasks/lists".equals(parent.getAction())) { assertThat(taskGroup.getChildTasks().size(), equalTo(1)); TaskGroup childGroup = taskGroup.getChildTasks().iterator().next(); assertThat(childGroup.getChildTasks().isEmpty(), equalTo(true)); - TaskInfo child = childGroup.getTaskInfo(); + org.elasticsearch.tasks.TaskInfo child = childGroup.getTaskInfo(); assertThat(child.getAction(), equalTo("cluster:monitor/tasks/lists[n]")); assertThat(child.getParentTaskId(), equalTo(parent.getTaskId())); listTasksFound = true; @@ -117,7 +116,7 @@ public void testGetValidTask() throws Exception { if (gtr.getWaitForCompletion()) { assertTrue(taskResponse.isCompleted()); } - TaskInfo info = taskResponse.getTaskInfo(); + org.elasticsearch.tasks.TaskInfo info = taskResponse.getTaskInfo(); assertTrue(info.isCancellable()); assertEquals("reindex from [source1] to [dest]", info.getDescription()); assertEquals("indices:data/write/reindex", info.getAction()); @@ -142,12 +141,12 @@ public void testCancelTasks() throws IOException { ); // in this case, probably no task will actually be cancelled. // this is ok, that case is covered in TasksIT.testTasksCancellation - TaskInfo firstTask = listResponse.getTasks().get(0); + org.elasticsearch.tasks.TaskInfo firstTask = listResponse.getTasks().get(0); String node = listResponse.getPerNodeTasks().keySet().iterator().next(); - CancelTasksRequest cancelTasksRequest = new CancelTasksRequest(); - cancelTasksRequest.setTaskId(new TaskId(node, firstTask.getId())); - cancelTasksRequest.setReason("testreason"); + CancelTasksRequest cancelTasksRequest = new CancelTasksRequest.Builder().withTaskId( + new TaskId(node, firstTask.getId()) + ).build(); CancelTasksResponse response = execute(cancelTasksRequest, highLevelClient().tasks()::cancel, highLevelClient().tasks()::cancelAsync); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/TasksRequestConvertersTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/TasksRequestConvertersTests.java index 4b7889d3b7e7a..054cd6a4ea009 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/TasksRequestConvertersTests.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/TasksRequestConvertersTests.java @@ -21,7 +21,6 @@ import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; -import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksRequest; import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksRequest; import org.elasticsearch.tasks.TaskId; import org.elasticsearch.test.ESTestCase; @@ -36,12 +35,16 @@ public class TasksRequestConvertersTests extends ESTestCase { public void testCancelTasks() { - CancelTasksRequest request = new CancelTasksRequest(); Map expectedParams = new HashMap<>(); - TaskId taskId = new TaskId(randomAlphaOfLength(5), randomNonNegativeLong()); - TaskId parentTaskId = new TaskId(randomAlphaOfLength(5), randomNonNegativeLong()); - request.setTaskId(taskId); - request.setParentTaskId(parentTaskId); + org.elasticsearch.client.tasks.TaskId taskId = + new org.elasticsearch.client.tasks.TaskId(randomAlphaOfLength(5), randomNonNegativeLong()); + org.elasticsearch.client.tasks.TaskId parentTaskId = + new org.elasticsearch.client.tasks.TaskId(randomAlphaOfLength(5), randomNonNegativeLong()); + org.elasticsearch.client.tasks.CancelTasksRequest request = + new org.elasticsearch.client.tasks.CancelTasksRequest.Builder() + .withTaskId(taskId) + .withParentTaskId(parentTaskId) + .build(); expectedParams.put("task_id", taskId.toString()); expectedParams.put("parent_task_id", parentTaskId.toString()); Request httpRequest = TasksRequestConverters.cancelTasks(request); diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/TasksClientDocumentationIT.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/TasksClientDocumentationIT.java index 56a7fce498c2a..d0bcb418e3659 100644 --- a/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/TasksClientDocumentationIT.java +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/documentation/TasksClientDocumentationIT.java @@ -23,14 +23,14 @@ import org.elasticsearch.action.ActionListener; import org.elasticsearch.action.LatchedActionListener; import org.elasticsearch.action.TaskOperationFailure; -import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksRequest; -import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksResponse; import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksRequest; import org.elasticsearch.action.admin.cluster.node.tasks.list.ListTasksResponse; import org.elasticsearch.action.admin.cluster.node.tasks.list.TaskGroup; import org.elasticsearch.client.ESRestHighLevelClientTestCase; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; +import org.elasticsearch.client.tasks.CancelTasksRequest; +import org.elasticsearch.client.tasks.CancelTasksResponse; import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.tasks.TaskId; import org.elasticsearch.tasks.TaskInfo; @@ -155,19 +155,21 @@ public void testCancelTasks() throws IOException { RestHighLevelClient client = highLevelClient(); { // tag::cancel-tasks-request - CancelTasksRequest request = new CancelTasksRequest(); + CancelTasksRequest request = new org.elasticsearch.client.tasks.CancelTasksRequest.Builder() + .withNodesFiltered(List.of("nodeId1", "nodeId2")) + .withActionsFiltered(List.of("cluster:*")) + .build(); // end::cancel-tasks-request // tag::cancel-tasks-request-filter - request.setTaskId(new TaskId("nodeId1", 42)); //<1> - request.setActions("cluster:*"); // <2> - request.setNodes("nodeId1", "nodeId2"); // <3> + CancelTasksRequest byTaskIdRequest = new org.elasticsearch.client.tasks.CancelTasksRequest.Builder() // <1> + .withTaskId(new org.elasticsearch.client.tasks.TaskId("myNode",44L)) // <2> + .build(); // <3> // end::cancel-tasks-request-filter } - CancelTasksRequest request = new CancelTasksRequest(); - request.setTaskId(TaskId.EMPTY_TASK_ID); + CancelTasksRequest request = new org.elasticsearch.client.tasks.CancelTasksRequest.Builder().build(); // tag::cancel-tasks-execute CancelTasksResponse response = client.tasks().cancel(request, RequestOptions.DEFAULT); @@ -176,18 +178,18 @@ public void testCancelTasks() throws IOException { assertThat(response, notNullValue()); // tag::cancel-tasks-response-tasks - List tasks = response.getTasks(); // <1> + List tasks = response.getTasks(); // <1> // end::cancel-tasks-response-tasks // tag::cancel-tasks-response-calc - Map> perNodeTasks = response.getPerNodeTasks(); // <1> - List groups = response.getTaskGroups(); // <2> + Map> perNodeTasks = response.getPerNodeTasks(); // <1> + List groups = response.getTaskGroups(); // <2> // end::cancel-tasks-response-calc // tag::cancel-tasks-response-failures - List nodeFailures = response.getNodeFailures(); // <1> - List taskFailures = response.getTaskFailures(); // <2> + List nodeFailures = response.getNodeFailures(); // <1> + List taskFailures = response.getTaskFailures(); // <2> // end::cancel-tasks-response-failures assertThat(response.getNodeFailures(), equalTo(emptyList())); @@ -198,7 +200,7 @@ public void testAsyncCancelTasks() throws InterruptedException { RestHighLevelClient client = highLevelClient(); { - CancelTasksRequest request = new CancelTasksRequest(); + CancelTasksRequest request = new org.elasticsearch.client.tasks.CancelTasksRequest.Builder().build(); // tag::cancel-tasks-execute-listener ActionListener listener = diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/tasks/CancelTasksResponseTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/tasks/CancelTasksResponseTests.java new file mode 100644 index 0000000000000..bdcbbd98e2830 --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/tasks/CancelTasksResponseTests.java @@ -0,0 +1,218 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.tasks; + +import org.elasticsearch.ElasticsearchException; +import org.elasticsearch.Version; +import org.elasticsearch.action.TaskOperationFailure; +import org.elasticsearch.action.admin.cluster.node.tasks.cancel.CancelTasksResponse; +import org.elasticsearch.client.AbstractResponseTestCase; +import org.elasticsearch.cluster.node.DiscoveryNode; +import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.transport.TransportAddress; +import org.elasticsearch.common.xcontent.XContentBuilder; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.tasks.TaskId; +import org.elasticsearch.tasks.TaskInfo; + +import java.io.IOException; +import java.net.InetAddress; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Collectors; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.emptySet; + +public class CancelTasksResponseTests extends AbstractResponseTestCase { + + private static String NODE_ID = "node_id"; + + @Override + protected CancelTasksResponseTests.ByNodeCancelTasksResponse createServerTestInstance(XContentType xContentType) { + List tasks = new ArrayList<>(); + List taskFailures = new ArrayList<>(); + List nodeFailures = new ArrayList<>(); + + for (int i = 0; i < randomIntBetween(1, 4); i++) { + taskFailures.add(new TaskOperationFailure(randomAlphaOfLength(4), (long) i, + new RuntimeException(randomAlphaOfLength(4)))); + } + for (int i = 0; i < randomIntBetween(1, 4); i++) { + nodeFailures.add(new ElasticsearchException(new RuntimeException(randomAlphaOfLength(10)))); + } + + for (int i = 0; i < 4; i++) { + tasks.add(new org.elasticsearch.tasks.TaskInfo( + new TaskId(NODE_ID, (long) i), + randomAlphaOfLength(4), + randomAlphaOfLength(4), + randomAlphaOfLength(10), + new FakeTaskStatus(randomAlphaOfLength(4), randomInt()), + randomLongBetween(1, 3), + randomIntBetween(5, 10), + false, + new TaskId("node1", randomLong()), + Map.of("x-header-of", "some-value"))); + } + + return new ByNodeCancelTasksResponse(tasks, taskFailures, nodeFailures); + } + + @Override + protected org.elasticsearch.client.tasks.CancelTasksResponse doParseToClientInstance(XContentParser parser) throws IOException { + return org.elasticsearch.client.tasks.CancelTasksResponse.fromXContent(parser); + } + + @Override + protected void assertInstances(ByNodeCancelTasksResponse serverTestInstance, + org.elasticsearch.client.tasks.CancelTasksResponse clientInstance) { + + // checking tasks + List sTasks = serverTestInstance.getTasks(); + List cTasks = clientInstance.getTasks(); + Map cTasksMap = + cTasks.stream().collect(Collectors.toMap(org.elasticsearch.client.tasks.TaskInfo::getTaskId, + Function.identity())); + for (TaskInfo ti : sTasks) { + org.elasticsearch.client.tasks.TaskInfo taskInfo = cTasksMap.get( + new org.elasticsearch.client.tasks.TaskId(ti.getTaskId().getNodeId(), ti.getTaskId().getId()) + ); + assertEquals(ti.getAction(), taskInfo.getAction()); + assertEquals(ti.getDescription(), taskInfo.getDescription()); + assertEquals(new HashMap<>(ti.getHeaders()), new HashMap<>(taskInfo.getHeaders())); + assertEquals(ti.getType(), taskInfo.getType()); + assertEquals(ti.getStartTime(), taskInfo.getStartTime()); + assertEquals(ti.getRunningTimeNanos(), taskInfo.getRunningTimeNanos()); + assertEquals(ti.isCancellable(), taskInfo.isCancellable()); + assertEquals(ti.getParentTaskId().getNodeId(), taskInfo.getParentTaskId().getNodeId()); + assertEquals(ti.getParentTaskId().getId(), taskInfo.getParentTaskId().getId()); + FakeTaskStatus status = (FakeTaskStatus) ti.getStatus(); + assertEquals(status.code, taskInfo.getStatus().get("code")); + assertEquals(status.status, taskInfo.getStatus().get("status")); + + } + + //checking failures + List serverNodeFailures = serverTestInstance.getNodeFailures(); + List cNodeFailures = clientInstance.getNodeFailures(); + List sExceptionsMessages = serverNodeFailures.stream().map(x -> + org.elasticsearch.client.tasks.ElasticsearchException.buildMessage( + "exception", x.getMessage(), null) + ).collect(Collectors.toList() + ); + + List cExceptionsMessages = cNodeFailures.stream().map( + org.elasticsearch.client.tasks.ElasticsearchException::getMsg + ).collect(Collectors.toList()); + assertEquals(new HashSet<>(sExceptionsMessages), new HashSet<>(cExceptionsMessages)); + + List sTaskFailures = serverTestInstance.getTaskFailures(); + List cTaskFailures = clientInstance.getTaskFailures(); + + Map cTasksFailuresMap = + cTaskFailures.stream().collect(Collectors.toMap( + org.elasticsearch.client.tasks.TaskOperationFailure::getTaskId, + Function.identity())); + for (TaskOperationFailure tof : sTaskFailures) { + org.elasticsearch.client.tasks.TaskOperationFailure failure = cTasksFailuresMap.get(tof.getTaskId()); + assertEquals(tof.getNodeId(), failure.getNodeId()); + assertTrue(failure.getReason().getMsg().contains("runtime_exception")); + assertTrue(failure.getStatus().contains("" + tof.getStatus().name())); + } + } + + public static class FakeTaskStatus implements Task.Status { + + final String status; + final int code; + + public FakeTaskStatus(String status, int code) { + this.status = status; + this.code = code; + } + + @Override + public String getWriteableName() { + return "faker"; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeString(status); + out.writeInt(code); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + builder.field("status", status); + builder.field("code", code); + return builder.endObject(); + } + } + + /** + * tasks are grouped under nodes, and in order to create DiscoveryNodes we need different + * IP addresses. + * So in this test we assume all tasks are issued by a single node whose name and IP address is hardcoded. + */ + static class ByNodeCancelTasksResponse extends CancelTasksResponse { + + ByNodeCancelTasksResponse(StreamInput in) throws IOException { + super(in); + } + + ByNodeCancelTasksResponse( + List tasks, + List taskFailures, + List nodeFailures) { + super(tasks, taskFailures, nodeFailures); + } + + + // it knows the hardcoded address space. + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + + DiscoveryNodes.Builder dnBuilder = new DiscoveryNodes.Builder(); + InetAddress inetAddress = InetAddress.getByAddress(new byte[]{(byte) 192, (byte) 168, (byte) 0, (byte) 1}); + TransportAddress transportAddress = new TransportAddress(inetAddress, randomIntBetween(0, 65535)); + + dnBuilder.add(new DiscoveryNode(NODE_ID, NODE_ID, transportAddress, emptyMap(), emptySet(), Version.CURRENT)); + + DiscoveryNodes build = dnBuilder.build(); + builder.startObject(); + super.toXContentGroupedByNode(builder, params, build); + builder.endObject(); + return builder; + } + } +} + + diff --git a/client/rest-high-level/src/test/java/org/elasticsearch/client/tasks/ElasticsearchExceptionTests.java b/client/rest-high-level/src/test/java/org/elasticsearch/client/tasks/ElasticsearchExceptionTests.java new file mode 100644 index 0000000000000..371f72fa660bd --- /dev/null +++ b/client/rest-high-level/src/test/java/org/elasticsearch/client/tasks/ElasticsearchExceptionTests.java @@ -0,0 +1,83 @@ +/* + * Licensed to Elasticsearch under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.elasticsearch.client.tasks; + +import org.elasticsearch.client.AbstractResponseTestCase; +import org.elasticsearch.common.xcontent.XContentParser; +import org.elasticsearch.common.xcontent.XContentType; + +import java.io.IOException; +import java.util.List; + +public class ElasticsearchExceptionTests extends AbstractResponseTestCase { + + @Override + protected org.elasticsearch.ElasticsearchException createServerTestInstance(XContentType xContentType) { + IllegalStateException ies = new IllegalStateException("illegal_state"); + IllegalArgumentException iae = new IllegalArgumentException("argument", ies); + org.elasticsearch.ElasticsearchException exception = new org.elasticsearch.ElasticsearchException("elastic_exception", iae); + exception.addHeader("key","value"); + exception.addMetadata("es.meta","data"); + exception.addSuppressed(new NumberFormatException("3/0")); + return exception; + } + + @Override + protected ElasticsearchException doParseToClientInstance(XContentParser parser) throws IOException { + parser.nextToken(); + return ElasticsearchException.fromXContent(parser); + } + + @Override + protected void assertInstances(org.elasticsearch.ElasticsearchException serverTestInstance, ElasticsearchException clientInstance) { + + IllegalArgumentException sCauseLevel1 = (IllegalArgumentException) serverTestInstance.getCause(); + ElasticsearchException cCauseLevel1 = clientInstance.getCause(); + + assertTrue(sCauseLevel1 !=null); + assertTrue(cCauseLevel1 !=null); + + IllegalStateException causeLevel2 = (IllegalStateException) serverTestInstance.getCause().getCause(); + ElasticsearchException cCauseLevel2 = clientInstance.getCause().getCause(); + assertTrue(causeLevel2 !=null); + assertTrue(cCauseLevel2 !=null); + + + ElasticsearchException cause = new ElasticsearchException( + "Elasticsearch exception [type=illegal_state_exception, reason=illegal_state]" + ); + ElasticsearchException caused1 = new ElasticsearchException( + "Elasticsearch exception [type=illegal_argument_exception, reason=argument]",cause + ); + ElasticsearchException caused2 = new ElasticsearchException( + "Elasticsearch exception [type=exception, reason=elastic_exception]",caused1 + ); + + caused2.addHeader("key", List.of("value")); + ElasticsearchException supp = new ElasticsearchException( + "Elasticsearch exception [type=number_format_exception, reason=3/0]" + ); + caused2.addSuppressed(List.of(supp)); + + assertEquals(caused2,clientInstance); + + } + +}