/**
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for
* license information.
*/
package com.microsoft.azure.batch;
import com.microsoft.azure.PagedList;
import com.microsoft.azure.batch.interceptor.BatchClientParallelOptions;
import com.microsoft.azure.batch.protocol.models.*;
import com.microsoft.rest.ServiceResponseWithHeaders;
import java.io.IOException;
import java.util.*;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.CopyOnWriteArrayList;
/**
* Performs task related operations on an Azure Batch account.
*/
public class TaskOperations implements IInheritedBehaviors {
TaskOperations(BatchClient batchClient, Collection<BatchClientBehavior> customBehaviors) {
_parentBatchClient = batchClient;
// inherit from instantiating parent
InternalHelper.InheritClientBehaviorsAndSetPublicProperty(this, customBehaviors);
}
private Collection<BatchClientBehavior> _customBehaviors;
private BatchClient _parentBatchClient;
/**
* Gets a list of behaviors that modify or customize requests to the Batch service.
*
* @return A list of BatchClientBehavior
*/
@Override
public Collection<BatchClientBehavior> customBehaviors() {
return _customBehaviors;
}
/**
* Sets a list of behaviors that modify or customize requests to the Batch service.
*
* @param behaviors The collection of BatchClientBehavior classes
* @return The current instance
*/
@Override
public IInheritedBehaviors withCustomBehaviors(Collection<BatchClientBehavior> behaviors) {
_customBehaviors = behaviors;
return this;
}
/**
* Adds a single task to a job.
*
* @param jobId The ID of the job to which to add the task.
* @param taskToAdd The {@link CloudTask} to add.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public void createTask(String jobId, TaskAddParameter taskToAdd) throws BatchErrorException, IOException {
createTask(jobId, taskToAdd, null);
}
/**
* Adds a single task to a job.
*
* @param jobId The ID of the job to which to add the task.
* @param taskToAdd The {@link CloudTask} to add.
* @param additionalBehaviors A collection of {@link BatchClientBehavior} instances that are applied to the Batch service request.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public void createTask(String jobId, TaskAddParameter taskToAdd, Iterable<BatchClientBehavior> additionalBehaviors) throws BatchErrorException, IOException {
TaskAddOptions options = new TaskAddOptions();
BehaviorManager bhMgr = new BehaviorManager(this.customBehaviors(), additionalBehaviors);
bhMgr.applyRequestBehaviors(options);
this._parentBatchClient.protocolLayer().tasks().add(jobId, taskToAdd, options);
}
/**
* Adds multiple tasks to a job.
*
* @param jobId The ID of the job to which to add the task.
* @param taskList A collection of {@link CloudTask tasks} to add.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
* @throws InterruptedException exception thrown if any thread has interrupted the current thread.
*/
public void createTasks(String jobId, List<TaskAddParameter> taskList) throws BatchErrorException, IOException, InterruptedException {
createTasks(jobId, taskList, null);
}
private static class WorkingThread implements Runnable {
final int MAX_TASKS_PER_REQUEST = 100;
private BatchClient client;
private BehaviorManager bhMgr;
private String jobId;
private Queue<TaskAddParameter> pendingList;
private List<TaskAddResult> failures;
private volatile Exception exception;
private final Object lock;
WorkingThread(BatchClient client, BehaviorManager bhMgr, String jobId, Queue<TaskAddParameter> pendingList, List<TaskAddResult> failures, Object lock) {
this.client = client;
this.bhMgr = bhMgr;
this.jobId = jobId;
this.pendingList = pendingList;
this.failures = failures;
this.exception = null;
this.lock = lock;
}
public Exception getException() {
return this.exception;
}
@Override
public void run() {
List<TaskAddParameter> taskList = new LinkedList<>();
// Take the task from the queue up to MAX_TASKS_PER_REQUEST
int count = 0;
while (count < MAX_TASKS_PER_REQUEST) {
TaskAddParameter param = pendingList.poll();
if (param != null) {
taskList.add(param);
count++;
}
else {
break;
}
}
if (taskList.size() > 0) {
// The option should be different to every server calls (for example, client-request-id)
TaskAddCollectionOptions options = new TaskAddCollectionOptions();
this.bhMgr.applyRequestBehaviors(options);
try {
ServiceResponseWithHeaders<TaskAddCollectionResult, TaskAddCollectionHeaders> response = this.client.protocolLayer().tasks().addCollection(this.jobId, taskList, options);
if (response.getBody() != null && response.getBody().value() != null) {
for (TaskAddResult result : response.getBody().value()) {
if (result.error() != null) {
if (result.status() == TaskAddStatus.SERVERERROR) {
// Server error will be retried
for (TaskAddParameter addParameter : taskList) {
if (addParameter.id().equals(result.taskId())) {
pendingList.add(addParameter);
break;
}
}
} else if (result.status() == TaskAddStatus.CLIENTERROR && !result.error().code().equals(BatchErrorCodeStrings.TaskExists)) {
// Client error will be recorded
failures.add(result);
}
}
}
}
} catch (BatchErrorException | IOException e) {
// Any exception will stop further call
exception = e;
pendingList.addAll(taskList);
}
}
synchronized (lock) {
// Notify main thread that sub thread finished
lock.notify();
}
}
}
/**
* Adds multiple tasks to a job.
*
* @param jobId The ID of the job to which to add the task.
* @param taskList A collection of {@link CloudTask tasks} to add.
* @param additionalBehaviors A collection of {@link BatchClientBehavior} instances that are applied to the Batch service request.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
* @throws InterruptedException exception thrown if any thread has interrupted the current thread.
*/
public void createTasks(String jobId, List<TaskAddParameter> taskList, Iterable<BatchClientBehavior> additionalBehaviors) throws BatchErrorException, IOException, InterruptedException {
BehaviorManager bhMgr = new BehaviorManager(this.customBehaviors(), additionalBehaviors);
// Default thread number is 1
int threadNumber = 1;
// Get user defined thread number
for (BatchClientBehavior op : bhMgr.getMasterListOfBehaviors()) {
if (op instanceof BatchClientParallelOptions) {
threadNumber = ((BatchClientParallelOptions)op).maxDegreeOfParallelism();
break;
}
}
final Object lock = new Object();
ConcurrentLinkedQueue<TaskAddParameter> pendingList = new ConcurrentLinkedQueue<>(taskList);
CopyOnWriteArrayList<TaskAddResult> failures = new CopyOnWriteArrayList<>();
Map<Thread, WorkingThread> threads = new HashMap<>();
Exception innerException = null;
while (!pendingList.isEmpty()) {
if (threads.size() < threadNumber) {
// Kick as many as possible add tasks requests by max allowed threads
WorkingThread worker = new WorkingThread(this._parentBatchClient, bhMgr, jobId, pendingList, failures, lock);
Thread thread = new Thread(worker);
thread.start();
threads.put(thread, worker);
}
else {
// Wait any thread finished
synchronized (lock) {
lock.wait();
}
List<Thread> finishedThreads = new ArrayList<>();
for (Thread t : threads.keySet()) {
if (t.getState() == Thread.State.TERMINATED) {
finishedThreads.add(t);
// If any exception happened, do not care the left requests
innerException = threads.get(t).getException();
if (innerException != null) {
break;
}
}
}
// Free thread pool, so we can start more threads to send the request
threads.keySet().removeAll(finishedThreads);
// Any errors happened, we stop
if (innerException != null || !failures.isEmpty()) {
break;
}
}
}
// May sure all the left threads finished
for (Thread t : threads.keySet()) {
t.join();
}
if (innerException == null) {
// Anything bad happened at the left threads?
for (Thread t : threads.keySet()) {
innerException = threads.get(t).getException();
if (innerException != null) {
break;
}
}
}
if (innerException != null) {
// We throw any exception happened in sub thread
if (innerException instanceof BatchErrorException) {
throw (BatchErrorException) innerException;
} else {
throw (IOException) innerException;
}
}
if (!failures.isEmpty()) {
// Report any client error with leftover request
List<TaskAddParameter> notFinished = new ArrayList<>();
for (TaskAddParameter param : pendingList) {
notFinished.add(param);
}
throw new CreateTasksTerminatedException("At least one task failed to be added.", failures, notFinished);
}
// We succeed here
}
/**
* Enumerates the {@link CloudTask tasks} of the specified job.
*
* @param jobId The ID of the job.
* @return A collection of {@link CloudTask tasks}.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public List<CloudTask> listTasks(String jobId) throws BatchErrorException, IOException {
return listTasks(jobId, null, null);
}
/**
* Enumerates the {@link CloudTask tasks} of the specified job.
*
* @param jobId The ID of the job.
* @param detailLevel A {@link DetailLevel} used for filtering the list and for controlling which properties are retrieved from the service.
* @return A collection of {@link CloudTask tasks}.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public List<CloudTask> listTasks(String jobId, DetailLevel detailLevel) throws BatchErrorException, IOException {
return listTasks(jobId, detailLevel, null);
}
/**
* Enumerates the {@link CloudTask tasks} of the specified job.
*
* @param jobId The ID of the job.
* @param detailLevel A {@link DetailLevel} used for filtering the list and for controlling which properties are retrieved from the service.
* @param additionalBehaviors A collection of {@link BatchClientBehavior} instances that are applied to the Batch service request.
* @return A collection of {@link CloudTask tasks}.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public List<CloudTask> listTasks(String jobId, DetailLevel detailLevel, Iterable<BatchClientBehavior> additionalBehaviors) throws BatchErrorException, IOException {
TaskListOptions options = new TaskListOptions();
BehaviorManager bhMgr = new BehaviorManager(this.customBehaviors(), additionalBehaviors);
bhMgr.appendDetailLevelToPerCallBehaviors(detailLevel);
bhMgr.applyRequestBehaviors(options);
ServiceResponseWithHeaders<PagedList<CloudTask>, TaskListHeaders> response = this._parentBatchClient.protocolLayer().tasks().list(jobId, options);
return response.getBody();
}
/**
* Enumerates the {@link SubtaskInformation subtask information} of the specified task.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @return A collection of {@link SubtaskInformation subtask information}.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public List<SubtaskInformation> listSubtasks(String jobId, String taskId) throws BatchErrorException, IOException {
return listSubtasks(jobId, taskId, null, null);
}
/**
* Enumerates the {@link SubtaskInformation subtask information} of the specified task.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @param detailLevel A {@link DetailLevel} used for filtering the list and for controlling which properties are retrieved from the service.
* @return A collection of {@link SubtaskInformation subtask information}.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public List<SubtaskInformation> listSubtasks(String jobId, String taskId, DetailLevel detailLevel) throws BatchErrorException, IOException {
return listSubtasks(jobId, taskId, detailLevel, null);
}
/**
* Enumerates the {@link SubtaskInformation subtask information} of the specified task.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @param detailLevel A {@link DetailLevel} used for filtering the list and for controlling which properties are retrieved from the service.
* @param additionalBehaviors A collection of {@link BatchClientBehavior} instances that are applied to the Batch service request.
* @return A collection of {@link SubtaskInformation subtask information}.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public List<SubtaskInformation> listSubtasks(String jobId, String taskId, DetailLevel detailLevel, Iterable<BatchClientBehavior> additionalBehaviors) throws BatchErrorException, IOException {
TaskListSubtasksOptions options = new TaskListSubtasksOptions();
BehaviorManager bhMgr = new BehaviorManager(this.customBehaviors(), additionalBehaviors);
bhMgr.appendDetailLevelToPerCallBehaviors(detailLevel);
bhMgr.applyRequestBehaviors(options);
ServiceResponseWithHeaders<CloudTaskListSubtasksResult, TaskListSubtasksHeaders> response = this._parentBatchClient.protocolLayer().tasks().listSubtasks(jobId, taskId, options);
if (response.getBody() != null) {
return response.getBody().value();
}
else {
return null;
}
}
/**
* Deletes the specified task.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public void deleteTask(String jobId, String taskId) throws BatchErrorException, IOException {
deleteTask(jobId, taskId, null);
}
/**
* Deletes the specified task.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @param additionalBehaviors A collection of {@link BatchClientBehavior} instances that are applied to the Batch service request.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public void deleteTask(String jobId, String taskId, Iterable<BatchClientBehavior> additionalBehaviors) throws BatchErrorException, IOException {
TaskDeleteOptions options = new TaskDeleteOptions();
BehaviorManager bhMgr = new BehaviorManager(this.customBehaviors(), additionalBehaviors);
bhMgr.applyRequestBehaviors(options);
this._parentBatchClient.protocolLayer().tasks().delete(jobId, taskId, options);
}
/**
* Gets the specified {@link CloudTask}.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @return A {@link CloudTask} containing information about the specified Azure Batch task.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public CloudTask getTask(String jobId, String taskId) throws BatchErrorException, IOException {
return getTask(jobId, taskId, null, null);
}
/**
* Gets the specified {@link CloudTask}.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @param detailLevel A {@link DetailLevel} used for filtering the list and for controlling which properties are retrieved from the service.
* @return A {@link CloudTask} containing information about the specified Azure Batch task.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public CloudTask getTask(String jobId, String taskId, DetailLevel detailLevel) throws BatchErrorException, IOException {
return getTask(jobId, taskId, detailLevel, null);
}
/**
* Gets the specified {@link CloudTask}.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @param detailLevel A {@link DetailLevel} used for filtering the list and for controlling which properties are retrieved from the service.
* @param additionalBehaviors A collection of {@link BatchClientBehavior} instances that are applied to the Batch service request.
* @return A {@link CloudTask} containing information about the specified Azure Batch task.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public CloudTask getTask(String jobId, String taskId, DetailLevel detailLevel, Iterable<BatchClientBehavior> additionalBehaviors) throws BatchErrorException, IOException {
TaskGetOptions options = new TaskGetOptions();
BehaviorManager bhMgr = new BehaviorManager(this.customBehaviors(), additionalBehaviors);
bhMgr.appendDetailLevelToPerCallBehaviors(detailLevel);
bhMgr.applyRequestBehaviors(options);
ServiceResponseWithHeaders<CloudTask, TaskGetHeaders> response = this._parentBatchClient.protocolLayer().tasks().get(jobId, taskId, options);
return response.getBody();
}
/**
* Updates the specified task.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @param constraints Constraints that apply to this task. If omitted, the task is given the default constraints.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public void updateTask(String jobId, String taskId, TaskConstraints constraints) throws BatchErrorException, IOException {
updateTask(jobId, taskId, constraints, null);
}
/**
* Updates the specified task.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @param constraints Constraints that apply to this task. If omitted, the task is given the default constraints.
* @param additionalBehaviors A collection of {@link BatchClientBehavior} instances that are applied to the Batch service request.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public void updateTask(String jobId, String taskId, TaskConstraints constraints, Iterable<BatchClientBehavior> additionalBehaviors) throws BatchErrorException, IOException {
TaskUpdateOptions options = new TaskUpdateOptions();
BehaviorManager bhMgr = new BehaviorManager(this.customBehaviors(), additionalBehaviors);
bhMgr.applyRequestBehaviors(options);
this._parentBatchClient.protocolLayer().tasks().update(jobId, taskId, constraints, options);
}
/**
* Terminates the specified task.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public void terminateTask(String jobId, String taskId) throws BatchErrorException, IOException {
terminateTask(jobId, taskId, null);
}
/**
* Terminates the specified task.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @param additionalBehaviors A collection of {@link BatchClientBehavior} instances that are applied to the Batch service request.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public void terminateTask(String jobId, String taskId, Iterable<BatchClientBehavior> additionalBehaviors) throws BatchErrorException, IOException {
TaskTerminateOptions options = new TaskTerminateOptions();
BehaviorManager bhMgr = new BehaviorManager(this.customBehaviors(), additionalBehaviors);
bhMgr.applyRequestBehaviors(options);
this._parentBatchClient.protocolLayer().tasks().terminate(jobId, taskId, options);
}
/**
* Reactivates a task, allowing it to run again even if its retry count has been exhausted.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public void reactivateTask(String jobId, String taskId) throws BatchErrorException, IOException {
reactivateTask(jobId, taskId, null);
}
/**
* Reactivates a task, allowing it to run again even if its retry count has been exhausted.
*
* @param jobId The ID of the job containing the task.
* @param taskId The ID of the task.
* @param additionalBehaviors A collection of {@link BatchClientBehavior} instances that are applied to the Batch service request.
* @throws BatchErrorException Exception thrown from REST call
* @throws IOException Exception thrown from serialization/deserialization
*/
public void reactivateTask(String jobId, String taskId, Iterable<BatchClientBehavior> additionalBehaviors) throws BatchErrorException, IOException {
TaskReactivateOptions options = new TaskReactivateOptions();
BehaviorManager bhMgr = new BehaviorManager(this.customBehaviors(), additionalBehaviors);
bhMgr.applyRequestBehaviors(options);
this._parentBatchClient.protocolLayer().tasks().reactivate(jobId, taskId, options);
}
}