/*
* Copyright (C) 2008 Universidade Federal de Campina Grande
*
* This file is part of OurGrid.
*
* OurGrid is free software: you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free
* Software Foundation, either version 3 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package org.ourgrid.common.job;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.ourgrid.common.interfaces.to.GridProcessExecutionResult;
import org.ourgrid.common.interfaces.to.GridProcessHandle;
import org.ourgrid.common.interfaces.to.GridProcessPhase;
import org.ourgrid.common.interfaces.to.GridProcessState;
import org.ourgrid.common.specification.job.TaskSpecification;
import org.ourgrid.worker.business.controller.GridProcessError;
/**
* A <code>Task</code> represents grid processes that compose the <code>Job</code>,
* <code>Task</code>s are replicated in the form of <code>Replica</code>s.
*
* @see Job
* @see GridProcess
*/
public class Task implements Serializable {
private static final long serialVersionUID = 40L;
/**
* <code>Job</code> that this <code>Task</code> belongs to.
*/
private Job job;
/**
* ID of this <code>Task</code>
*/
private int taskid;
/**
* Specification for this <code>Task</code>
*/
private TaskSpecification taskSpec;
/**
* Actual number of fails that occurred on this task
*/
private int actualFails;
/**
* Current state of this task.
*/
private GridProcessState state;
/**
* Replicas of this task.
*/
private ArrayList<GridProcess> gridProcesses;
/**
* Creation time of this Task
*/
private long creationTime;
/**
* Time when this Task finished its grid process
*/
private long finalizationTime;
/**
* Constant used to indicated that this task has not been finalized.
*/
public static final int TIME_NOT_FINALIZED = -1;
/**
* Creates a new <code>Task</code>.
*
* @param jobid ID of the <code>Job</code> which this task belongs to.
* @param taskid ID of this <code>Task</code>.
* @param taskSpec Specification that defines this <code>Task</code> and
* <code>Replica</code>s of this <code>Task</code>.
* @param maxFails Maximum number of <code>Replica</code>s that may fail
* on this <code>Task</code>.
* @param maxReplicas Maximum number of replicas that may run simultaneously
* for this <code>Task</code>.
*/
public Task( Job job, int taskid, TaskSpecification taskSpec ) {
this.job = job;
this.taskid = taskid;
this.taskSpec = taskSpec;
this.state = GridProcessState.UNSTARTED;
this.gridProcesses = new ArrayList<GridProcess>();
this.creationTime = System.currentTimeMillis();
this.finalizationTime = TIME_NOT_FINALIZED;
this.actualFails = 0;
}
/**
* Informs the <code>Task</code> the result of a <code>Replica</code>.
*
* @param replicaResult Result of the <code>Replica</code>.
* @param newState New state of the <code>Replica</code>.
* @param maxFailed
*/
public void newReplicaResult( GridProcessExecutionResult replicaResult, GridProcessState newState, boolean maxFailed,
boolean canReplicate) {
GridProcess replica = getReplicaByID( replicaResult.getReplicaHandle().getReplicaID() );
if ( replica != null ) {
boolean hasFinishedReplica = hasAnySisterReplicaFinished(replicaResult.getReplicaHandle());
if ( GridProcessState.FINISHED.equals( newState ) && hasFinishedReplica ) {
throw new IllegalResultException( "Task can not have two " + GridProcessState.FINISHED + " Replicas",
job.getJobId(), taskid );
}
if ( GridProcessState.ABORTED.equals( newState ) && !hasFinishedReplica ) {
throw new IllegalResultException( "Task can not have " + GridProcessState.ABORTED
+ " Replicas without a " + GridProcessState.FINISHED + " Replica", job.getJobId(), taskid );
}
replica.setGridProcessState( newState );
if ( !GridProcessState.CANCELLED.equals( state ) ) {
updateState( replica, maxFailed, canReplicate );
if (GridProcessState.FINISHED.equals(state) || GridProcessState.FAILED.equals(state)) {
this.finalizationTime = System.currentTimeMillis();
}
}
}
}
/**
* Updates the state of this <code>Task</code> according to the last
* <code>Replica</code> that had it's state changed.
*
* @param replica Last <code>Replica</code> to change state.
* @param maxFailed
*/
private void updateState( GridProcess replica, boolean maxFailed, boolean canReplicate ) {
if ( GridProcessState.FINISHED.equals( replica.getState() ) || GridProcessState.ABORTED.equals( replica.getState() ) ) {
boolean stillRunning = hasGridProcessInExecution() || canReplicate;
if ( stillRunning ) {
state = GridProcessState.RUNNING;
} else {
state = GridProcessState.FINISHED;
}
} else {
if ( replica.getState().equals( GridProcessState.FAILED ) ) { //sabotage or failure
GridProcessError ee = replica.getResult().getExecutionError();
// Only errors caused by the user increment the max fails. Even if the actualFails
//meets the maxFails, a task will not enter the FAILED state if there are replica
//running, but the task will not be able to replicate. @see Task#canReplicate()
if(ee != null) {
if(ee.getType().causedByUserApplication()) {
incrementFails();
}
if(maxFailed) {
state = GridProcessState.FAILED;
}
else {
state = hasFinishedReplica() && !hasGridProcessInExecution() ? GridProcessState.FINISHED
: GridProcessState.RUNNING;
}
}else {
state = hasFinishedReplica() && !hasGridProcessInExecution() ? GridProcessState.FINISHED
: GridProcessState.RUNNING;
}
}
}
}
/**
* Increments the number of fails that occurred on this <code>Task</code>.
*
* @return Current number of fails that occurred on the <code>Task</code>.
*/
private int incrementFails() {
return ++actualFails;
}
/**
* Searches for any replica that is either in state
* <code>ExecutionState.UNSTARTED</code> or
* <code>ExecutionState.RUNNING</code>.
*
* @return True if any replicas are on either state.
*/
public boolean hasGridProcessInExecution() {
for ( GridProcess replica : gridProcesses ) {
if ( GridProcessState.RUNNING.equals( replica.getState() )
|| GridProcessState.UNSTARTED.equals( replica.getState() ) ) {
return true;
}
}
return false;
}
/**
* Searches for any replica that is in state
* <code>ExecutionState.FINISHED</code>.
*
* @return True if any replicas are on the state.
*/
public boolean hasFinishedReplica() {
for ( GridProcess replica : gridProcesses ) {
if ( GridProcessState.FINISHED.equals( replica.getState() ) ) {
return true;
}
}
return false;
}
/**
* Searches for any replica that is in state
* <code>ExecutionState.RUNNING</code>.
*
* @return True if any replicas are on the state.
*/
public boolean hasRunnigGridProcess() {
for ( GridProcess replica : gridProcesses ) {
if ( GridProcessState.RUNNING.equals( replica.getState() ) ) {
return true;
}
}
return false;
}
public boolean hasAnySisterReplicaFinished(GridProcessHandle handle) {
for ( GridProcess replica : gridProcesses ) {
if ( !handle.equals(replica.getHandle()) && GridProcessState.FINISHED.equals( replica.getState() ) ) {
return true;
}
}
return false;
}
/**
* Set's this <code>Task</code> as cancelled, meaning that it will no
* longer be replicated.
*/
public void setAsCancelled() {
if ( GridProcessState.RUNNING.equals( state ) || GridProcessState.UNSTARTED.equals( state ) ) {
this.state = GridProcessState.CANCELLED;
this.finalizationTime = System.currentTimeMillis();
}
}
/**
* Creates a new <code>Replica</code> of this <code>Task</code> if
* allowed. A new <code>Replica</code> can be created if the task is still
* in the <code>RUNNING</code> state and the current number of running
* <code>Replica</code>s is lesser than the <code>maxreplicas</code>
* defined for this <code>Task</code>.
*
* @return Newly created <code>Replica</code> or <code>null</code> if
* one could not be created.
*/
protected GridProcess createNewReplica() {
state = GridProcessState.RUNNING;
GridProcess replica = new GridProcess( gridProcesses.size() + 1, this);
gridProcesses.add( replica );
return replica;
}
/**
* Get's <code>Replica</code> according to the given taskid.
*
* @param replicaid ID of the replica.
* @return The <code>Replica</code> that has such id or null.
* @see GridProcess
*/
public GridProcess getReplicaByID( int replicaid ) {
return gridProcesses.get( replicaid - 1 );
}
/**
* Get's the id for this <code>Task</code>.
*
* @return ID of this <code>Task</code>.
*/
public int getTaskid() {
return taskid;
}
/**
* @return The <code>Job</code> that this <code>Task</code>
* belongs to.
*/
public Job getJob() {
return this.job;
}
/**
* Get's the specification of this <code>Task</code>.
*
* @return <code>TaskSpec</code> determining the specification for this
* <code>Task</code>.
*/
public TaskSpecification getSpec() {
return taskSpec;
}
/**
* Returns the current state of this <code>Task</code>.
*
* @return State of this <code>Task</code>.
*/
public GridProcessState getState() {
return state;
}
public int getNumberOfRunningReplicas() {
int returnValue = 0;
for ( GridProcess replica : gridProcesses ) {
if ( GridProcessState.RUNNING.equals( replica.getState() ) ) {
++returnValue;
}
}
return returnValue;
}
public Collection<GridProcess> getReadyToRunGridProcesses() {
Set<GridProcess> readyToRunExecs = new LinkedHashSet<GridProcess>();
for (GridProcess gridProcess : this.gridProcesses) {
if (gridProcess.isReadyToRun()) {
readyToRunExecs.add(gridProcess);
}
}
return readyToRunExecs;
}
public List<GridProcess> getGridProcesses() {
return gridProcesses;
}
public int getActualFails() {
return actualFails;
}
@Override
public String toString() {
return getJob().getJobId() + "." + getTaskid();
}
public void replicaPhaseUpdate( GridProcessHandle replicaHandle, GridProcessPhase newPhase ) {
GridProcess replica = getReplicaByID( replicaHandle.getReplicaID() );
if ( replica != null ) {
replica.gridProcessPhaseUpdate( newPhase );
}
}
/**
* Return the timestamp on which the task was added
*
* @return timestamp in milliseconds since epoch
* @see System#currentTimeMillis()
*/
public long getCreationTime() {
return this.creationTime;
}
/**
* Return the timestamp on which task was finished
*
* @return timestamp in milliseconds since epoch
* @see System#currentTimeMillis()
*/
public long getFinalizationTime() {
return finalizationTime;
}
public int getJobId() {
return getJob().getJobId();
}
}