package org.ovirt.engine.core.bll;
import static org.ovirt.engine.core.common.config.ConfigValues.UknownTaskPrePollingLapse;
import org.ovirt.engine.core.common.asynctasks.AsyncTaskParameters;
import org.ovirt.engine.core.common.businessentities.AsyncTaskStatus;
import org.ovirt.engine.core.common.businessentities.AsyncTaskStatusEnum;
import org.ovirt.engine.core.common.config.Config;
import org.ovirt.engine.core.common.errors.VdcBllErrors;
import org.ovirt.engine.core.common.vdscommands.SPMTaskGuidBaseVDSCommandParameters;
import org.ovirt.engine.core.common.vdscommands.VDSCommandType;
import org.ovirt.engine.core.common.vdscommands.VDSReturnValue;
import org.ovirt.engine.core.compat.Guid;
import org.ovirt.engine.core.compat.LogCompat;
import org.ovirt.engine.core.compat.LogFactoryCompat;
import org.ovirt.engine.core.dal.dbbroker.DbFacade;
public class SPMAsyncTask {
public SPMAsyncTask(AsyncTaskParameters parameters) {
setParameters(parameters);
setState(AsyncTaskState.Initializing);
AddOrUpdateTaskInDB();
}
private AsyncTaskParameters privateParameters;
public AsyncTaskParameters getParameters() {
return privateParameters;
}
public void setParameters(AsyncTaskParameters value) {
privateParameters = value;
}
public Guid getTaskID() {
return getParameters().getTaskID();
}
public Guid getStoragePoolID() {
return getParameters().getStoragePoolID();
}
private AsyncTaskState privateState = AsyncTaskState.forValue(0);
public AsyncTaskState getState() {
return privateState;
}
public void setState(AsyncTaskState value) {
privateState = value;
}
public boolean getShouldPoll() {
AsyncTaskState state = getState();
return (state == AsyncTaskState.Polling || state == AsyncTaskState.Ended || state == AsyncTaskState.ClearFailed)
&& getLastTaskStatus().getStatus() != AsyncTaskStatusEnum.unknown
&& (getParameters().getEntityId() == null ? isTaskOverPrePollingLapse() : true);
}
private AsyncTaskStatus _lastTaskStatus = new AsyncTaskStatus(AsyncTaskStatusEnum.init);
public AsyncTaskStatus getLastTaskStatus() {
return _lastTaskStatus;
}
/**
* Set the _lastTaskStatus with taskStatus.
*
* @param taskStatus
* - task status to set.
*/
protected void setLastTaskStatus(AsyncTaskStatus taskStatus) {
_lastTaskStatus = taskStatus;
}
/**
* Update task last access date ,only for not active task.
*/
public void setLastStatusAccessTime() {
// Change access date to now , when task is not active.
if (getState() == AsyncTaskState.Ended
|| getState() == AsyncTaskState.AttemptingEndAction
|| getState() == AsyncTaskState.ClearFailed
|| getState() == AsyncTaskState.Cleared) {
_lastAccessToStatusSinceEnd = System.currentTimeMillis();
}
}
// Indicates time in milliseconds when task status recently changed.
protected long _lastAccessToStatusSinceEnd = System.currentTimeMillis();
public long getLastAccessToStatusSinceEnd() {
return _lastAccessToStatusSinceEnd;
}
public Object getContainerId() {
return getParameters().getEntityId();
}
private void AddOrUpdateTaskInDB() {
try {
if (getParameters().getDbAsyncTask() != null) {
if (DbFacade.getInstance().getAsyncTaskDAO().get(getTaskID()) == null) {
log.infoFormat("BaseAsyncTask::AddOrUpdateTaskInDB: Adding task {0} to DataBase", getTaskID());
DbFacade.getInstance().getAsyncTaskDAO().save(getParameters().getDbAsyncTask());
} else {
DbFacade.getInstance().getAsyncTaskDAO().update(getParameters().getDbAsyncTask());
}
}
} catch (RuntimeException e) {
log.error(String.format(
"BaseAsyncTask::AddOrUpdateTaskInDB: Adding/Updating task %1$s to DataBase threw an exception.",
getTaskID()), e);
}
}
public void UpdateAsyncTask() {
AddOrUpdateTaskInDB();
}
public void StartPollingTask() {
AsyncTaskState state = getState();
if (state != AsyncTaskState.AttemptingEndAction
&& state != AsyncTaskState.Cleared
&& state != AsyncTaskState.ClearFailed) {
log.infoFormat("BaseAsyncTask::StartPollingTask: Starting to poll task '{0}'.", getTaskID());
ConcreteStartPollingTask();
}
}
/**
* Use this to hold unknown tasks from polling, to overcome bz673695 without a complete re-haul to the
* AsyncTaskManager and CommandBase.
* @TODO remove this and re-factor {@link AsyncTaskManager}
* @return true when the time passed after creating the task is bigger than
* <code>ConfigValues.UknownTaskPrePollingLapse</code>
* @see AsyncTaskManager
* @see CommandBase
* @since 3.0
*/
boolean isTaskOverPrePollingLapse() {
AsyncTaskParameters parameters = getParameters();
long taskStartTime = parameters.getDbAsyncTask().getaction_parameters().getTaskStartTime();
Integer prePollingPeriod = Config.<Integer> GetValue(UknownTaskPrePollingLapse);
boolean idlePeriodPassed =
System.currentTimeMillis() - taskStartTime > prePollingPeriod;
log.infoFormat("task id {0} {1}. Pre-polling period is {2} millis. ",
parameters.getTaskID(),
idlePeriodPassed ? "has passed pre-polling period time and should be polled"
: "is in pre-polling period and should not be polled", prePollingPeriod);
return idlePeriodPassed;
}
protected void ConcreteStartPollingTask() {
setState(AsyncTaskState.Polling);
}
/**
* For each task set its updated status retrieved from VDSM.
*
* @param returnTaskStatus
* - Task status returned from VDSM.
*/
public void UpdateTask(AsyncTaskStatus returnTaskStatus) {
try {
switch (getState()) {
case Polling:
// Get the returned task
returnTaskStatus = CheckTaskExist(returnTaskStatus);
if (returnTaskStatus.getStatus() != getLastTaskStatus().getStatus()) {
AddLogStatusTask(returnTaskStatus);
}
setLastTaskStatus(returnTaskStatus);
if (!getLastTaskStatus().getTaskIsRunning()) {
HandleEndedTask();
}
break;
case Ended:
HandleEndedTask();
break;
// Try to clear task which failed to be cleared before SPM and DB
case ClearFailed:
ClearAsyncTask();
break;
}
}
catch (RuntimeException e) {
log.error(
String.format(
"BaseAsyncTask::PollAndUpdateTask: Handling task '%1$s' (State: %2$s, Parent Command: %3$s, Parameters Type: %4$s) threw an exception",
getTaskID(),
getState(),
(getParameters().getDbAsyncTask()
.getaction_type()),
getParameters()
.getClass().getName()),
e);
}
}
/**
* Handle ended task operation. Change task state to Ended ,Cleared or
* Cleared Failed , and log appropriate message.
*/
private void HandleEndedTask() {
// If task state is different from Ended chnage it to Ended and set the
// last access time to now.
if (getState() != AsyncTaskState.Ended) {
setState(AsyncTaskState.Ended);
setLastStatusAccessTime();
}
if (HasTaskEndedSuccessfully()) {
OnTaskEndSuccess();
}
else if (HasTaskEndedInFailure()) {
OnTaskEndFailure();
}
else if (!DoesTaskExist()) {
OnTaskDoesNotExist();
}
}
protected void RemoveTaskFromDB() {
try {
if (DbFacade.getInstance().getAsyncTaskDAO().get(getTaskID()) != null) {
log.infoFormat("BaseAsyncTask::RemoveTaskFromDB: Removing task {0} from DataBase", getTaskID());
DbFacade.getInstance().getAsyncTaskDAO().remove(getTaskID());
}
}
catch (RuntimeException e) {
log.error(String.format(
"BaseAsyncTask::RemoveTaskFromDB: Removing task %1$s from DataBase threw an exception.",
getTaskID()), e);
}
}
private boolean HasTaskEndedSuccessfully() {
return getLastTaskStatus().getTaskEndedSuccessfully();
}
private boolean HasTaskEndedInFailure() {
return !getLastTaskStatus().getTaskIsRunning() && !getLastTaskStatus().getTaskEndedSuccessfully();
}
private boolean DoesTaskExist() {
return getLastTaskStatus().getStatus() != AsyncTaskStatusEnum.unknown;
}
protected void OnTaskEndSuccess() {
LogEndTaskSuccess();
ClearAsyncTask();
}
protected void LogEndTaskSuccess() {
log.infoFormat(
"BaseAsyncTask::OnTaskEndSuccess: Task '{0}' (Parent Command {1}, Parameters Type {2}) ended successfully.",
getTaskID(),
(getParameters().getDbAsyncTask().getaction_type()),
getParameters()
.getClass().getName());
}
protected void OnTaskEndFailure() {
LogEndTaskFailure();
ClearAsyncTask();
}
protected void LogEndTaskFailure() {
log.errorFormat(
"BaseAsyncTask::LogEndTaskFailure: Task '{0}' (Parent Command {1}, Parameters Type {2}) ended with failure:"
+ "\r\n" + "-- Result: '{3}'" + "\r\n" + "-- Message: '{4}'," + "\r\n" + "-- Exception: '{5}'",
getTaskID(),
(getParameters().getDbAsyncTask().getaction_type()),
getParameters()
.getClass().getName(),
getLastTaskStatus().getResult(),
(getLastTaskStatus().getMessage() == null ? "[null]" : getLastTaskStatus().getMessage()),
(getLastTaskStatus()
.getException() == null ? "[null]" : getLastTaskStatus().getException().getMessage()));
}
protected void OnTaskDoesNotExist() {
LogTaskDoesntExist();
ClearAsyncTask();
}
protected void LogTaskDoesntExist() {
log.errorFormat(
"BaseAsyncTask::LogTaskDoesntExist: Task '{0}' (Parent Command {1}, Parameters Type {2}) does not exist.",
getTaskID(),
(getParameters().getDbAsyncTask().getaction_type()),
getParameters()
.getClass().getName());
}
/**
* Print log message, Checks if the cachedStatusTask is null, (indicating the task was not found in the SPM).
* If so returns {@link AsyncTaskStatusEnum#running} status, otherwise returns the status as given.<br>
* <br>
* <b>Note:</b> The task is returned as running since we need to support a case where there is a change of SPM,
* or the SPM is just recovering from crash, and the SPM might return that it doesn't know that this task exists,
* but in actuality it exists. If in this case {@link AsyncTaskStatusEnum#unknown} is returned then the task
* will become a permanent zombie task since it won't be polled, so take notice if you ever want to change this
* behavior.
*
* @param cachedStatusTask The status from the SPM, or <code>null</code> is the task wasn't found in the SPM.
* @return - Updated status task
*/
protected AsyncTaskStatus CheckTaskExist(AsyncTaskStatus cachedStatusTask) {
AsyncTaskStatus returnedStatusTask = null;
// If the cachedStatusTask is null ,that means the task has not been found in the SPM.
if (cachedStatusTask == null) {
// Set to running in order to continue polling the task in case SPM hasn't loaded the tasks yet..
returnedStatusTask = new AsyncTaskStatus(AsyncTaskStatusEnum.running);
if (getLastTaskStatus().getStatus() != returnedStatusTask.getStatus()) {
log.errorFormat("SPMAsyncTask::PollTask: Task '{0}' (Parent Command {1}, Parameters Type {2}) " +
"was not found in VDSM, will change its status to running.",
getTaskID(), (getParameters().getDbAsyncTask().getaction_type()),
getParameters().getClass().getName());
}
} else {
returnedStatusTask = cachedStatusTask;
}
return returnedStatusTask;
}
/**
* Prints a log message of the task status,
*
* @param cachedStatusTask
* - Status got from VDSM
*/
protected void AddLogStatusTask(AsyncTaskStatus cachedStatusTask) {
String formatString = "SPMAsyncTask::PollTask: Polling task '{0}' (Parent Command {1}, Parameters Type {2}) "
+ "returned status '{3}'{4}.";
// If task doesn't exist (unknown) or has ended with failure (aborting)
// , log warn.
if (cachedStatusTask.getTaskIsInUnusualState()) {
log.warnFormat(
formatString,
getTaskID(),
(getParameters().getDbAsyncTask()
.getaction_type()),
getParameters().getClass().getName(),
cachedStatusTask.getStatus(),
((cachedStatusTask.getStatus() == AsyncTaskStatusEnum.finished) ? (String
.format(", result '%1$s'",
cachedStatusTask.getResult())) : ("")));
}
else {
log.infoFormat(
formatString,
getTaskID(),
(getParameters().getDbAsyncTask()
.getaction_type()),
getParameters().getClass().getName(),
cachedStatusTask.getStatus(),
((cachedStatusTask.getStatus() == AsyncTaskStatusEnum.finished) ? (String
.format(", result '%1$s'",
cachedStatusTask.getResult())) : ("")));
}
}
protected AsyncTaskStatus PollTask() {
AsyncTaskStatus returnValue = null;
try {
Object tempVar = Backend
.getInstance()
.getResourceManager()
.RunVdsCommand(VDSCommandType.SPMGetTaskStatus,
new SPMTaskGuidBaseVDSCommandParameters(getStoragePoolID(), getTaskID())).getReturnValue();
returnValue = (AsyncTaskStatus) ((tempVar instanceof AsyncTaskStatus) ? tempVar : null);
}
catch (RuntimeException e) {
log.error(
String.format(
"SPMAsyncTask::PollTask: Polling task '%1$s' (Parent Command %2$s, Parameters Type %3$s) threw an exception, task is still considered running.",
getTaskID(),
(getParameters().getDbAsyncTask().getaction_type()),
getParameters().getClass().getName()),
e);
}
if (returnValue == null) {
log.errorFormat(
"SPMAsyncTask::PollTask: Polling task '{0}' (Parent Command {1}, Parameters Type {2}) failed, task is still considered running.",
getTaskID(),
(getParameters().getDbAsyncTask().getaction_type()),
getParameters()
.getClass().getName());
AsyncTaskStatus tempVar2 = new AsyncTaskStatus();
tempVar2.setStatus(AsyncTaskStatusEnum.running);
returnValue = tempVar2;
}
String formatString =
"SPMAsyncTask::PollTask: Polling task '{0}' (Parent Command {1}, Parameters Type {2}) returned status '{3}'{4}.";
if (returnValue.getTaskIsInUnusualState()) {
log.warnFormat(
formatString,
getTaskID(),
(getParameters().getDbAsyncTask().getaction_type()),
getParameters().getClass().getName(),
returnValue.getStatus(),
((returnValue.getStatus() == AsyncTaskStatusEnum.finished) ? (String.format(", result '%1$s'",
returnValue.getResult())) : ("")));
}
else {
log.infoFormat(
formatString,
getTaskID(),
(getParameters().getDbAsyncTask().getaction_type()),
getParameters().getClass().getName(),
returnValue.getStatus(),
((returnValue.getStatus() == AsyncTaskStatusEnum.finished) ? (String.format(", result '%1$s'",
returnValue.getResult())) : ("")));
}
return returnValue;
}
public void StopTask() {
if (getState() != AsyncTaskState.AttemptingEndAction && getState() != AsyncTaskState.Cleared
&& getState() != AsyncTaskState.ClearFailed) {
try {
log.infoFormat(
"SPMAsyncTask::StopTask: Attempting to stop task '{0}' (Parent Command {1}, Parameters Type {2}).",
getTaskID(),
(getParameters().getDbAsyncTask().getaction_type()),
getParameters().getClass().getName());
Backend.getInstance()
.getResourceManager()
.RunVdsCommand(VDSCommandType.SPMStopTask,
new SPMTaskGuidBaseVDSCommandParameters(getStoragePoolID(), getTaskID()));
} catch (RuntimeException e) {
log.error(
String.format("SPMAsyncTask::StopTask: Stopping task '%1$s' threw an exception.", getTaskID()),
e);
} finally {
setState(AsyncTaskState.Polling);
}
}
}
public void ClearAsyncTask() {
VDSReturnValue vdsReturnValue = null;
try {
log.infoFormat("SPMAsyncTask::ClearAsyncTask: Attempting to clear task '{0}'", getTaskID());
vdsReturnValue = Backend
.getInstance()
.getResourceManager()
.RunVdsCommand(VDSCommandType.SPMClearTask,
new SPMTaskGuidBaseVDSCommandParameters(getStoragePoolID(), getTaskID()));
}
catch (RuntimeException e) {
log.error(String.format("SPMAsyncTask::ClearAsyncTask: Clearing task '%1$s' threw an exception.",
getTaskID()), e);
}
if (!isTaskStateError(vdsReturnValue)) {
if (vdsReturnValue == null || !vdsReturnValue.getSucceeded()) {
setState(AsyncTaskState.ClearFailed);
OnTaskCleanFailure();
} else {
setState(AsyncTaskState.Cleared);
RemoveTaskFromDB();
}
}
}
/**
* Function return true if we got error 410 - which is SPM initializing and
* we did not clear the task
*
* @param vdsReturnValue
* @return
*/
private boolean isTaskStateError(VDSReturnValue vdsReturnValue) {
if (vdsReturnValue != null && vdsReturnValue.getVdsError() != null
&& vdsReturnValue.getVdsError().getCode() == VdcBllErrors.TaskStateError) {
log.infoFormat(
"SPMAsyncTask::ClearAsyncTask: At time of attemp to clear task '{0}' the response code was {2} and message was {3}. Task will not be cleaned",
getTaskID(),
vdsReturnValue.getVdsError().getCode(),
vdsReturnValue.getVdsError().getMessage());
return true;
}
return false;
}
protected void OnTaskCleanFailure() {
LogTaskCleanFailure();
}
protected void LogTaskCleanFailure() {
log.errorFormat("SPMAsyncTask::ClearAsyncTask: Clearing task '{0}' failed.", getTaskID());
}
private static LogCompat log = LogFactoryCompat.getLog(SPMAsyncTask.class);
}