package org.ovirt.engine.core.bll;
import java.util.List;
import org.ovirt.engine.core.bll.job.ExecutionHandler;
import org.ovirt.engine.core.bll.tasks.CommandCoordinatorUtil;
import org.ovirt.engine.core.bll.tasks.interfaces.CommandCallback;
import org.ovirt.engine.core.common.action.VdcActionParametersBase.EndProcedure;
import org.ovirt.engine.core.common.action.VdcReturnValueBase;
import org.ovirt.engine.core.common.businessentities.CommandEntity;
import org.ovirt.engine.core.common.errors.EngineError;
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.compat.backendcompat.CommandExecutionStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public abstract class ChildCommandsCallbackBase implements CommandCallback {
protected final Logger log = LoggerFactory.getLogger(getClass());
@Override
public void doPolling(Guid cmdId, List<Guid> childCmdIds) {
CommandExecutionStatus status = CommandCoordinatorUtil.getCommandExecutionStatus(cmdId);
// TODO: should be removed when doPolling will be moved to run only after execute finish - here for test purpose
// only.
if (status != CommandExecutionStatus.EXECUTED &&
CommandCoordinatorUtil.getCommandStatus(cmdId) == CommandStatus.ACTIVE) {
return;
}
boolean anyFailed = false;
int completedChildren = 0;
CommandBase<?> command = getCommand(cmdId);
for (Guid childCmdId : childCmdIds) {
CommandBase<?> child = getCommand(childCmdId);
switch (CommandCoordinatorUtil.getCommandStatus(childCmdId)) {
case NOT_STARTED:
case ACTIVE:
case EXECUTION_FAILED:
logWaitingForChildCommand(child, command);
return;
case FAILED:
if (shouldWaitForEndMethodsCompletion(child, command)) {
return;
}
anyFailed = true;
break;
case ENDED_WITH_FAILURE:
case UNKNOWN:
anyFailed = true;
break;
case SUCCEEDED:
if (shouldWaitForEndMethodsCompletion(child, command)) {
return;
}
default:
++completedChildren;
}
}
childCommandsExecutionEnded(command, anyFailed, childCmdIds, status, completedChildren);
}
private boolean shouldCommandEndOnAsyncOpEnd(CommandBase<?> cmd) {
return cmd.getParameters().getEndProcedure() == EndProcedure.COMMAND_MANAGED;
}
private boolean shouldWaitForEndMethodsCompletion(CommandBase<?> childCommand, CommandBase<?> parentCommand) {
CommandEntity cmdEntity = CommandCoordinatorUtil.getCommandEntity(childCommand.getCommandId());
boolean hasNotifiedCallback = cmdEntity.isCallbackEnabled() && cmdEntity.isCallbackNotified();
if (shouldCommandEndOnAsyncOpEnd(childCommand) && !hasNotifiedCallback) {
logWaitingForChildCommand(childCommand, parentCommand);
return true;
}
return false;
}
private void logWaitingForChildCommand(CommandBase<?> childCommand, CommandBase<?> parentCommand) {
log.info("Command '{}' (id: '{}') waiting on child command id: '{}' type:'{}' to complete",
parentCommand.getActionType(),
parentCommand.getCommandId(),
childCommand.getCommandId(),
childCommand.getActionType());
}
protected void setCommandEndStatus(CommandBase<?> command, boolean childCommandFailed,
CommandExecutionStatus status, List<Guid> childCmdIds) {
command.getParameters().setTaskGroupSuccess(!childCommandFailed && status == CommandExecutionStatus.EXECUTED);
CommandStatus newStatus = command.getParameters().getTaskGroupSuccess() ? CommandStatus.SUCCEEDED
: CommandStatus.FAILED;
log.info("Command '{}' id: '{}' child commands '{}' executions were completed, status '{}'",
command.getActionType(), command.getCommandId(), childCmdIds, newStatus);
if (!shouldExecuteEndMethod(command)) {
logEndWillBeExecutedByParent(command, newStatus);
}
command.setCommandStatus(newStatus, false);
command.persistCommand(command.getParameters().getParentCommand(), command.getCallback() != null);
}
protected abstract void childCommandsExecutionEnded(CommandBase<?> command,
boolean anyFailed,
List<Guid> childCmdIds,
CommandExecutionStatus status,
int completedChildren);
protected boolean shouldExecuteEndMethod(CommandBase<?> commandBase) {
return !commandBase.isExecutedAsChildCommand()
|| shouldCommandEndOnAsyncOpEnd(commandBase);
}
private void endAction(CommandBase<?> commandBase, boolean succeeded) {
if (shouldExecuteEndMethod(commandBase)) {
commandBase.getReturnValue().setSucceeded(false);
VdcReturnValueBase returnVal = commandBase.endAction();
if (!returnVal.getSucceeded()) {
if (shouldRepeatEndMethodsOnFail(returnVal)) {
throw new EngineException(EngineError.ENGINE, String.format("Command %1$s id: '%2$s' endAction() " +
"didn't complete successfully", commandBase.getActionType(), commandBase.getCommandId()));
} else {
log.warn("Command '{}' id: '{}' end method execution failed, as the command isn't marked for " +
"endAction() retries silently ignoring", commandBase.getActionType(),
commandBase.getCommandId());
}
}
if (!commandBase.isExecutedAsChildCommand()) {
CommandCoordinatorUtil.removeAllCommandsInHierarchy(commandBase.getCommandId());
}
ExecutionHandler.getInstance().endJob(commandBase.getExecutionContext(), succeeded);
}
}
@Override
public void onSucceeded(Guid cmdId, List<Guid> childCmdIds) {
endAction(getCommand(cmdId), true);
}
@Override
public void onFailed(Guid cmdId, List<Guid> childCmdIds) {
CommandBase<?> commandBase = getCommand(cmdId);
// This should be removed as soon as infra bug will be fixed and failed execution will reach endWithFailure
commandBase.getParameters().setTaskGroupSuccess(false);
endAction(commandBase, false);
}
protected CommandBase<?> getCommand(Guid cmdId) {
return CommandCoordinatorUtil.retrieveCommand(cmdId);
}
public void logEndWillBeExecutedByParent(CommandBase<?> command, CommandStatus status) {
log.info(
"Command '{}' id: '{}' Updating status to '{}', The command end method logic will be executed by one of its parent commands.",
command.getActionType(),
command.getCommandId(),
status);
}
@Override
public boolean pollOnExecutionFailed() {
return true;
}
@Override
public boolean shouldRepeatEndMethodsOnFail(Guid cmdId) {
return shouldRepeatEndMethodsOnFail(CommandCoordinatorUtil.getCommandEntity(cmdId).getReturnValue());
}
private boolean shouldRepeatEndMethodsOnFail(VdcReturnValueBase returnValue) {
return returnValue.getEndActionTryAgain();
}
}