package org.ovirt.engine.core.bll.tasks;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.ovirt.engine.core.bll.CommandBase;
import org.ovirt.engine.core.bll.CommandMultiAsyncTasks;
import org.ovirt.engine.core.bll.CommandsFactory;
import org.ovirt.engine.core.bll.job.ExecutionContext;
import org.ovirt.engine.core.bll.tasks.interfaces.CommandCoordinator;
import org.ovirt.engine.core.common.action.VdcActionParametersBase;
import org.ovirt.engine.core.common.action.VdcActionType;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.asynctasks.AsyncTaskParameters;
import org.ovirt.engine.core.common.asynctasks.EndedTaskInfo;
import org.ovirt.engine.core.common.businessentities.AsyncTask;
import org.ovirt.engine.core.common.businessentities.CommandEntity;
import org.ovirt.engine.core.common.errors.EngineException;
import org.ovirt.engine.core.compat.CommandStatus;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.utils.threadpool.ThreadPoolUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Base class for all tasks regarding a specific command.
*/
public class CommandAsyncTask extends SPMAsyncTask {
private static final Logger log = LoggerFactory.getLogger(CommandAsyncTask.class);
private static final Object _lockObject = new Object();
private static final Map<Guid, CommandMultiAsyncTasks> _multiTasksByCommandIds = new HashMap<>();
public CommandMultiAsyncTasks getCommandMultiAsyncTasks() {
CommandMultiAsyncTasks entityInfo = null;
synchronized (_lockObject) {
entityInfo = _multiTasksByCommandIds.get(getCommandId());
}
return entityInfo;
}
public CommandAsyncTask(CommandCoordinator coco, AsyncTaskParameters parameters, boolean duringInit) {
super(coco, parameters);
boolean isNewCommandAdded = false;
synchronized (_lockObject) {
if (!_multiTasksByCommandIds.containsKey(getCommandId())) {
log.info("CommandAsyncTask::Adding CommandMultiAsyncTasks object for command '{}'",
getCommandId());
_multiTasksByCommandIds.put(getCommandId(), new CommandMultiAsyncTasks(getCommandId()));
isNewCommandAdded = true;
}
CommandMultiAsyncTasks entityInfo = getCommandMultiAsyncTasks();
entityInfo.attachTask(this);
}
if (duringInit && isNewCommandAdded) {
CommandBase<?> command =
CommandsFactory.createCommand(parameters.getDbAsyncTask().getActionType(),
parameters.getDbAsyncTask().getActionParameters());
if (!command.acquireLockAsyncTask()) {
log.warn("Failed to acquire locks for command '{}' with parameters '{}'",
parameters.getDbAsyncTask().getActionType(),
parameters.getDbAsyncTask().getActionParameters());
}
}
}
@Override
public void concreteStartPollingTask() {
CommandMultiAsyncTasks entityInfo = getCommandMultiAsyncTasks();
entityInfo.startPollingTask(getVdsmTaskId());
}
@Override
protected void onTaskEndSuccess() {
logEndTaskSuccess();
endActionIfNecessary();
}
private void endActionIfNecessary() {
CommandMultiAsyncTasks entityInfo = getCommandMultiAsyncTasks();
if (entityInfo == null) {
log.warn("CommandAsyncTask::endActionIfNecessary: No info is available for entity '{}', current"
+ " task ('{}') was probably created while other tasks were in progress, clearing task.",
getCommandId(),
getVdsmTaskId());
clearAsyncTask();
}
else if (entityInfo.shouldEndAction() && !hasRunningChildCommands()) {
log.info(
"CommandAsyncTask::endActionIfNecessary: All tasks of command '{}' has ended -> executing 'endAction'",
getCommandId());
log.info("CommandAsyncTask::endAction: Ending action for '{}' tasks (command ID: '{}'): calling endAction '.",
entityInfo.getTasksCountCurrentActionType(),
entityInfo.getCommandId());
entityInfo.markAllWithAttemptingEndAction();
ThreadPoolUtil.execute(() -> endCommandAction());
}
}
private boolean hasRunningChildCommands() {
Guid rootCmdId = getParameters().getDbAsyncTask().getRootCommandId();
for (CommandEntity entity : coco.getChildCmdsByRootCmdId(rootCmdId)) {
if (!hasCompleted(entity) && !coco.doesCommandContainAsyncTask(entity.getId())) {
return true;
}
}
return false;
}
private boolean hasCompleted(CommandEntity entity) {
return CommandStatus.SUCCEEDED.equals(entity.getCommandStatus()) ||
CommandStatus.FAILED.equals(entity.getCommandStatus()) ||
CommandStatus.ENDED_WITH_FAILURE.equals(entity.getCommandStatus()) ||
CommandStatus.ENDED_SUCCESSFULLY.equals(entity.getCommandStatus()) ||
CommandStatus.EXECUTION_FAILED.equals(entity.getCommandStatus());
}
private void endCommandAction() {
CommandMultiAsyncTasks entityInfo = getCommandMultiAsyncTasks();
VdcReturnValueBase vdcReturnValue = null;
ExecutionContext context = null;
boolean endActionRuntimeException = false;
AsyncTask dbAsyncTask = getParameters().getDbAsyncTask();
ArrayList<VdcActionParametersBase> imagesParameters = new ArrayList<>();
for (EndedTaskInfo taskInfo : entityInfo.getEndedTasksInfo().getTasksInfo()) {
VdcActionParametersBase childTaskParameters =
taskInfo.getTaskParameters().getDbAsyncTask().getTaskParameters();
boolean childTaskGroupSuccess =
childTaskParameters.getTaskGroupSuccess() && taskInfo.getTaskStatus().getTaskEndedSuccessfully();
childTaskParameters
.setTaskGroupSuccess(childTaskGroupSuccess);
if (!childTaskParameters.equals(dbAsyncTask.getActionParameters())) {
imagesParameters.add(childTaskParameters);
}
}
dbAsyncTask.getActionParameters().setImagesParameters(imagesParameters);
try {
log.info("CommandAsyncTask::endCommandAction [within thread] context: Attempting to endAction '{}',",
dbAsyncTask.getActionParameters().getCommandType());
try {
vdcReturnValue = coco.endAction(this);
} catch (EngineException ex) {
log.error("{}: {}", getErrorMessage(), ex.getMessage());
log.debug("Exception", ex);
} catch (RuntimeException ex) {
log.error(getErrorMessage(), ex);
endActionRuntimeException = true;
}
}
catch (RuntimeException Ex2) {
log.error("CommandAsyncTask::endCommandAction [within thread]: An exception has been thrown (not"
+ " related to 'endAction' itself)",
Ex2);
endActionRuntimeException = true;
}
finally {
// if a RuntimeExcpetion occurs we clear the task from db and perform no other action
if (endActionRuntimeException) {
handleEndActionRuntimeException(entityInfo, dbAsyncTask);
} else {
boolean isTaskGroupSuccess = dbAsyncTask.getActionParameters().getTaskGroupSuccess();
handleEndActionResult(entityInfo, vdcReturnValue, context, isTaskGroupSuccess);
}
}
}
private String getErrorMessage() {
return String.format("[within thread]: endAction for action type %1$s threw an exception.",
getParameters().getDbAsyncTask().getActionParameters().getCommandType());
}
private void handleEndActionRuntimeException(CommandMultiAsyncTasks commandInfo, AsyncTask dbAsyncTask) {
try {
VdcActionType actionType = getParameters().getDbAsyncTask().getActionType();
log.info("CommandAsyncTask::HandleEndActionResult: endAction for action type '{}' threw an"
+ " unrecoverable RuntimeException the task will be cleared.",
actionType);
commandInfo.clearTaskByVdsmTaskId(dbAsyncTask.getVdsmTaskId());
removeTaskFromDB();
if (commandInfo.getAllCleared()) {
log.info("CommandAsyncTask::HandleEndActionRuntimeException: Removing CommandMultiAsyncTasks"
+ " object for entity '{}'",
commandInfo.getCommandId());
synchronized (_lockObject) {
_multiTasksByCommandIds.remove(commandInfo.getCommandId());
}
}
}
catch (RuntimeException ex) {
log.error("CommandAsyncTask::HandleEndActionResult [within thread]: an exception has been thrown", ex);
}
}
private void handleEndActionResult(CommandMultiAsyncTasks commandInfo, VdcReturnValueBase vdcReturnValue,
ExecutionContext context,
boolean isTaskGroupSuccess) {
try {
VdcActionType actionType = getParameters().getDbAsyncTask().getActionType();
log.info("CommandAsyncTask::HandleEndActionResult [within thread]: endAction for action type '{}'"
+ " completed, handling the result.",
actionType);
if (vdcReturnValue == null || (!vdcReturnValue.getSucceeded() && vdcReturnValue.getEndActionTryAgain())) {
log.info("CommandAsyncTask::HandleEndActionResult [within thread]: endAction for action type"
+ " '{}' hasn't succeeded, not clearing tasks, will attempt again next polling.",
actionType);
commandInfo.repoll();
}
else {
log.info("CommandAsyncTask::HandleEndActionResult [within thread]: endAction for action type"
+ " '{}' {}succeeded, clearing tasks.",
actionType,
vdcReturnValue.getSucceeded() ? "" : "hasn't ");
commandInfo.clearTasks();
synchronized (_lockObject) {
if (commandInfo.getAllCleared()) {
log.info("CommandAsyncTask::HandleEndActionResult [within thread]: Removing"
+ " CommandMultiAsyncTasks object for entity '{}'",
commandInfo.getCommandId());
_multiTasksByCommandIds.remove(commandInfo.getCommandId());
}
}
}
}
catch (RuntimeException ex) {
log.error("CommandAsyncTask::HandleEndActionResult [within thread]: an exception has been thrown", ex);
}
}
@Override
protected void onTaskEndFailure() {
logEndTaskFailure();
endActionIfNecessary();
}
@Override
protected void onTaskDoesNotExist() {
logTaskDoesntExist();
endActionIfNecessary();
}
}