/**
* 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.FalconException;
import org.apache.falcon.entity.v0.process.Process;
import org.apache.falcon.exception.InvalidStateTransitionException;
import org.apache.falcon.exception.StateStoreException;
import org.apache.falcon.execution.ProcessExecutionInstance;
import org.apache.falcon.state.store.AbstractStateStore;
import org.apache.falcon.util.StateStoreProperties;
import org.joda.time.DateTime;
import org.mockito.Mockito;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
/**
* Tests the state changes of an instance.
*/
public class InstanceStateServiceTest {
private InstanceStateChangeHandler listener = Mockito.mock(InstanceStateChangeHandler.class);
private ProcessExecutionInstance mockInstance;
@BeforeClass
public void init() {
StateStoreProperties.get().setProperty("falcon.state.store.impl",
"org.apache.falcon.state.store.InMemoryStateStore");
}
@BeforeMethod
public void setup() {
Process testProcess = new Process();
testProcess.setName("test");
// Setup new mocks so we can verify the no. of invocations
mockInstance = Mockito.mock(ProcessExecutionInstance.class);
Mockito.when(mockInstance.getEntity()).thenReturn(testProcess);
Mockito.when(mockInstance.getCreationTime()).thenReturn(DateTime.now());
Mockito.when(mockInstance.getInstanceTime()).thenReturn(DateTime.now());
Mockito.when(mockInstance.getCluster()).thenReturn("testCluster");
}
@AfterMethod
public void tearDown() throws StateStoreException {
AbstractStateStore.get().clear();
}
// Tests an entity instance's lifecycle : Trigger -> waiting -> ready -> running
// -> suspendAll -> resumeAll -> success
@Test
public void testLifeCycle() throws FalconException {
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.TRIGGER, listener);
InstanceState instanceFromStore = AbstractStateStore.get()
.getExecutionInstance(new InstanceID(mockInstance));
Mockito.verify(listener).onTrigger(mockInstance);
Assert.assertTrue(instanceFromStore.getCurrentState().equals(InstanceState.STATE.WAITING));
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.CONDITIONS_MET, listener);
Mockito.verify(listener).onConditionsMet(mockInstance);
instanceFromStore = AbstractStateStore.get()
.getExecutionInstance(new InstanceID(mockInstance));
Assert.assertTrue(instanceFromStore.getCurrentState().equals(InstanceState.STATE.READY));
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.SCHEDULE, listener);
Mockito.verify(listener).onSchedule(mockInstance);
instanceFromStore = AbstractStateStore.get()
.getExecutionInstance(new InstanceID(mockInstance));
Assert.assertTrue(instanceFromStore.getCurrentState().equals(InstanceState.STATE.RUNNING));
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.SUSPEND, listener);
Mockito.verify(listener).onSuspend(mockInstance);
instanceFromStore = AbstractStateStore.get()
.getExecutionInstance(new InstanceID(mockInstance));
Assert.assertTrue(instanceFromStore.getCurrentState().equals(InstanceState.STATE.SUSPENDED));
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.RESUME_RUNNING, listener);
Mockito.verify(listener).onResume(mockInstance);
instanceFromStore = AbstractStateStore.get()
.getExecutionInstance(new InstanceID(mockInstance));
Assert.assertTrue(instanceFromStore.getCurrentState().equals(InstanceState.STATE.RUNNING));
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.SUCCEED, listener);
Mockito.verify(listener).onSuccess(mockInstance);
instanceFromStore = AbstractStateStore.get()
.getExecutionInstance(new InstanceID(mockInstance));
Assert.assertTrue(instanceFromStore.getCurrentState().equals(InstanceState.STATE.SUCCEEDED));
Assert.assertEquals(AbstractStateStore.get().getAllEntities().size(), 0);
}
@Test
public void testInvalidTransitions() throws FalconException {
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.TRIGGER, listener);
try {
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.SCHEDULE, listener);
Assert.fail("Exception expected");
} catch (InvalidStateTransitionException e) {
// Do nothing
}
// Resume an instance that is not suspended
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.CONDITIONS_MET, listener);
try {
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.RESUME_READY, listener);
Assert.fail("Exception expected");
} catch (InvalidStateTransitionException e) {
// Do nothing
}
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.SCHEDULE, listener);
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.FAIL, listener);
// Attempt killing a completed instance
try {
StateService.get().handleStateChange(mockInstance, InstanceState.EVENT.KILL, listener);
Assert.fail("Exception expected");
} catch (InvalidStateTransitionException e) {
// Do nothing
}
}
@Test(dataProvider = "state_and_events")
public void testIdempotency(InstanceState.STATE state, InstanceState.EVENT event)
throws InvalidStateTransitionException, StateStoreException {
InstanceState instanceState = new InstanceState(mockInstance).setCurrentState(state);
instanceState.nextTransition(event);
Assert.assertEquals(instanceState.getCurrentState(), state);
}
@DataProvider(name = "state_and_events")
public Object[][] stateAndEvents() {
return new Object[][] {
{InstanceState.STATE.WAITING, InstanceState.EVENT.TRIGGER},
{InstanceState.STATE.READY, InstanceState.EVENT.CONDITIONS_MET},
{InstanceState.STATE.TIMED_OUT, InstanceState.EVENT.TIME_OUT},
{InstanceState.STATE.RUNNING, InstanceState.EVENT.SCHEDULE},
{InstanceState.STATE.SUSPENDED, InstanceState.EVENT.SUSPEND},
{InstanceState.STATE.KILLED, InstanceState.EVENT.KILL},
{InstanceState.STATE.SUCCEEDED, InstanceState.EVENT.SUCCEED},
{InstanceState.STATE.FAILED, InstanceState.EVENT.FAIL},
};
}
}