/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.falcon.state;
import org.apache.falcon.exception.InvalidStateTransitionException;
import org.apache.falcon.execution.ExecutionInstance;
import java.util.ArrayList;
import java.util.List;
/**
* Represents the state of an execution instance.
* Implements {@link org.apache.falcon.state.StateMachine} for an instance.
*/
public class InstanceState implements StateMachine<InstanceState.STATE, InstanceState.EVENT> {
private ExecutionInstance instance;
private STATE currentState;
private static final STATE INITIAL_STATE = STATE.WAITING;
/**
* Enumerates all the valid states of an instance and the valid transitions from that state.
*/
public enum STATE implements StateMachine<InstanceState.STATE, InstanceState.EVENT> {
WAITING {
@Override
public STATE nextTransition(EVENT event) throws InvalidStateTransitionException {
switch (event) {
case SUSPEND:
return SUSPENDED;
case KILL:
return KILLED;
case CONDITIONS_MET:
return READY;
case TIME_OUT:
return TIMED_OUT;
case TRIGGER:
return this;
case EXTERNAL_TRIGGER:
return this;
case FAIL:
return FAILED;
default:
throw new InvalidStateTransitionException("Event " + event.name() + " not valid for state, "
+ STATE.WAITING.name());
}
}
},
READY {
@Override
public STATE nextTransition(EVENT event) throws InvalidStateTransitionException {
switch (event) {
case SUSPEND:
return SUSPENDED;
case KILL:
return KILLED;
case SCHEDULE:
return RUNNING;
case CONDITIONS_MET:
return this;
case FAIL:
return FAILED;
default:
throw new InvalidStateTransitionException("Event " + event.name()
+ " not valid for state, " + this.name());
}
}
},
RUNNING {
@Override
public STATE nextTransition(EVENT event) throws InvalidStateTransitionException {
switch (event) {
case SUSPEND:
return SUSPENDED;
case KILL:
return KILLED;
case SUCCEED:
return SUCCEEDED;
case FAIL:
return FAILED;
case SCHEDULE:
return this;
default:
throw new InvalidStateTransitionException("Event " + event.name()
+ " not valid for state, " + this.name());
}
}
}, SUCCEEDED {
@Override
public STATE nextTransition(EVENT event) throws InvalidStateTransitionException {
if (event == EVENT.SUCCEED) {
return this;
}
if (event == EVENT.EXTERNAL_TRIGGER) {
return WAITING;
}
throw new InvalidStateTransitionException("Instance is in terminal state, " + this.name()
+ ". Cannot apply transitions.");
}
},
FAILED {
@Override
public STATE nextTransition(EVENT event) throws InvalidStateTransitionException {
if (event == EVENT.FAIL) {
return this;
}
if (event == EVENT.EXTERNAL_TRIGGER) {
return WAITING;
}
throw new InvalidStateTransitionException("Instance is in terminal state, " + this.name()
+ ". Cannot apply transitions.");
}
},
KILLED {
@Override
public STATE nextTransition(EVENT event) throws InvalidStateTransitionException {
if (event == EVENT.KILL) {
return this;
}
if (event == EVENT.EXTERNAL_TRIGGER) {
return WAITING;
}
throw new InvalidStateTransitionException("Instance is in terminal state, " + this.name()
+ ". Cannot apply transitions.");
}
},
TIMED_OUT {
@Override
public STATE nextTransition(EVENT event) throws InvalidStateTransitionException {
if (event == EVENT.TIME_OUT) {
return this;
}
if (event == EVENT.EXTERNAL_TRIGGER) {
return WAITING;
}
throw new InvalidStateTransitionException("Instance is in terminal state, " + this.name()
+ ". Cannot apply transitions.");
}
},
SUSPENDED {
@Override
public STATE nextTransition(EVENT event) throws InvalidStateTransitionException {
switch (event) {
case RESUME_WAITING:
return WAITING;
case RESUME_READY:
return READY;
case RESUME_RUNNING:
return RUNNING;
case SUSPEND:
return this;
// The instance can complete execution on DAG engine, just after a suspend was issued.
// Especially with Oozie, it finishes execution of current action before suspending.
// Hence need to allow terminal states too.
case SUCCEED:
return SUCCEEDED;
case FAIL:
return FAILED;
case KILL:
return KILLED;
default:
throw new InvalidStateTransitionException("Event " + event.name()
+ " not valid for state, " + this.name());
}
}
}
}
/**
* Enumerates all the valid events that can cause a state transition.
*/
public enum EVENT {
TRIGGER,
CONDITIONS_MET,
TIME_OUT,
SCHEDULE,
SUSPEND,
RESUME_WAITING,
RESUME_READY,
RESUME_RUNNING,
KILL,
SUCCEED,
FAIL,
EXTERNAL_TRIGGER
}
/**
* Constructor.
*
* @param instance
*/
public InstanceState(ExecutionInstance instance) {
this.instance = instance;
currentState = INITIAL_STATE;
}
/**
* @return execution instance
*/
public ExecutionInstance getInstance() {
return instance;
}
/**
* @return current state
*/
public STATE getCurrentState() {
return currentState;
}
/**
* @param state
* @return This instance
*/
public InstanceState setCurrentState(STATE state) {
this.currentState = state;
return this;
}
@Override
public STATE nextTransition(EVENT event) throws InvalidStateTransitionException {
return currentState.nextTransition(event);
}
/**
* @return "active" states of an instance.
*/
public static List<STATE> getActiveStates() {
List<InstanceState.STATE> states = new ArrayList<STATE>();
states.add(STATE.RUNNING);
states.add(STATE.READY);
states.add(STATE.WAITING);
return states;
}
/**
* @return "running" states of an instance.
*/
public static List<STATE> getRunningStates() {
List<InstanceState.STATE> states = new ArrayList<STATE>();
states.add(STATE.RUNNING);
return states;
}
/**
* @return "terminal" states of an instance.
*/
public static List<STATE> getTerminalStates() {
List<InstanceState.STATE> states = new ArrayList<STATE>();
states.add(STATE.FAILED);
states.add(STATE.KILLED);
states.add(STATE.SUCCEEDED);
states.add(STATE.TIMED_OUT);
return states;
}
@Override
public String toString() {
StringBuilder output = new StringBuilder();
if (instance.getId() != null) {
output.append(instance.getId());
}
return output.append("STATE").append(currentState.toString()).toString();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
InstanceState other = (InstanceState) o;
if (this.getCurrentState() != null ? !this.getCurrentState().equals(other.getCurrentState())
: other.getCurrentState() != null) {
return false;
}
if (this.getInstance() != null ? !this.getInstance().equals(other.getInstance())
: other.getInstance() != null) {
return false;
}
return true;
}
@Override
public int hashCode() {
int result = currentState != null ? currentState.hashCode() : 0;
result = 31 * result + (instance != null ? instance.hashCode() : 0);
return result;
}
}