/*
* 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 gobblin.runtime.api;
import static gobblin.configuration.ConfigurationKeys.JOB_NAME_KEY;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import java.util.Properties;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.Test;
import com.google.common.base.Optional;
import com.google.common.base.Predicate;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;
import gobblin.configuration.ConfigurationKeys;
import gobblin.runtime.JobState.RunningState;
import gobblin.runtime.std.JobExecutionUpdatable;
import gobblin.testing.AssertWithBackoff;
import gobblin.util.ExecutorsUtils;
/**
* Unit tests for {@link JobExecutionState}
*/
public class TestJobExecutionState {
@Test public void testStateTransitionsSuccess() throws TimeoutException, InterruptedException {
final Logger log = LoggerFactory.getLogger(getClass().getSimpleName() + ".testStateTransitionsSuccess");
JobSpec js1 = JobSpec.builder("gobblin:/testStateTransitionsSuccess/job1")
.withConfig(ConfigFactory.empty()
.withValue(ConfigurationKeys.JOB_NAME_KEY, ConfigValueFactory.fromAnyRef("myJob")))
.build();
JobExecution je1 = JobExecutionUpdatable.createFromJobSpec(js1);
final JobExecutionStateListener listener = mock(JobExecutionStateListener.class);
final JobExecutionState jes1 =
new JobExecutionState(js1, je1, Optional.<JobExecutionStateListener>absent());
// Current state is null
assertFailedStateTransition(jes1, RunningState.RUNNING);
assertFailedStateTransition(jes1, RunningState.COMMITTED);
assertFailedStateTransition(jes1, RunningState.SUCCESSFUL);
assertFailedStateTransition(jes1, RunningState.FAILED);
assertFailedStateTransition(jes1, RunningState.RUNNING);
assertFailedStateTransition(jes1, RunningState.CANCELLED);
assertTransition(jes1, listener, null, RunningState.PENDING, log);
// Current state is PENDING
assertFailedStateTransition(jes1, RunningState.PENDING);
assertFailedStateTransition(jes1, RunningState.COMMITTED);
assertFailedStateTransition(jes1, RunningState.SUCCESSFUL);
assertTransition(jes1, listener, RunningState.PENDING, RunningState.RUNNING, log);
// Current state is RUNNING
assertFailedStateTransition(jes1, RunningState.PENDING);
assertFailedStateTransition(jes1, RunningState.COMMITTED);
assertFailedStateTransition(jes1, RunningState.RUNNING);
assertTransition(jes1, listener, RunningState.RUNNING, RunningState.SUCCESSFUL, log);
// Current state is SUCCESSFUL
assertFailedStateTransition(jes1, RunningState.PENDING);
assertFailedStateTransition(jes1, RunningState.RUNNING);
assertFailedStateTransition(jes1, RunningState.SUCCESSFUL);
assertTransition(jes1, listener, RunningState.SUCCESSFUL, RunningState.COMMITTED, log);
// Current state is COMMITTED (final)
assertFailedStateTransition(jes1, RunningState.RUNNING);
assertFailedStateTransition(jes1, RunningState.COMMITTED);
assertFailedStateTransition(jes1, RunningState.SUCCESSFUL);
assertFailedStateTransition(jes1, RunningState.FAILED);
assertFailedStateTransition(jes1, RunningState.RUNNING);
assertFailedStateTransition(jes1, RunningState.CANCELLED);
}
@Test public void testStateTransitionsFailure() throws TimeoutException, InterruptedException {
final Logger log = LoggerFactory.getLogger(getClass().getSimpleName() + ".testStateTransitionsFailure");
JobSpec js1 = JobSpec.builder("gobblin:/testStateTransitionsFailure/job1")
.withConfig(ConfigFactory.empty()
.withValue(ConfigurationKeys.JOB_NAME_KEY, ConfigValueFactory.fromAnyRef("myJob")))
.build();
JobExecution je1 = JobExecutionUpdatable.createFromJobSpec(js1);
final JobExecutionStateListener listener = mock(JobExecutionStateListener.class);
final JobExecutionState jes1 =
new JobExecutionState(js1, je1, Optional.<JobExecutionStateListener>absent());
assertTransition(jes1, listener, null, RunningState.PENDING, log);
assertTransition(jes1, listener, RunningState.PENDING, RunningState.FAILED, log);
// Current state is FAILED (final)
assertFailedStateTransition(jes1, RunningState.RUNNING);
assertFailedStateTransition(jes1, RunningState.COMMITTED);
assertFailedStateTransition(jes1, RunningState.SUCCESSFUL);
assertFailedStateTransition(jes1, RunningState.FAILED);
assertFailedStateTransition(jes1, RunningState.RUNNING);
assertFailedStateTransition(jes1, RunningState.CANCELLED);
final JobExecutionState jes2 =
new JobExecutionState(js1, je1, Optional.<JobExecutionStateListener>absent());
assertTransition(jes2, listener, null, RunningState.PENDING, log);
assertTransition(jes2, listener, RunningState.PENDING, RunningState.RUNNING, log);
assertTransition(jes2, listener, RunningState.RUNNING, RunningState.FAILED, log);
final JobExecutionState je3 =
new JobExecutionState(js1, je1, Optional.<JobExecutionStateListener>absent());
assertTransition(je3, listener, null, RunningState.PENDING, log);
assertTransition(je3, listener, RunningState.PENDING, RunningState.RUNNING, log);
assertTransition(je3, listener, RunningState.RUNNING, RunningState.SUCCESSFUL, log);
assertTransition(je3, listener, RunningState.SUCCESSFUL, RunningState.FAILED, log);
}
@Test public void testStateTransitionsCancel() throws TimeoutException, InterruptedException {
final Logger log = LoggerFactory.getLogger(getClass().getSimpleName() + ".testStateTransitionsCancel");
JobSpec js1 = JobSpec.builder("gobblin:/testStateTransitionsCancel/job1")
.withConfig(ConfigFactory.empty()
.withValue(ConfigurationKeys.JOB_NAME_KEY, ConfigValueFactory.fromAnyRef("myJob")))
.build();
JobExecution je1 = JobExecutionUpdatable.createFromJobSpec(js1);
final JobExecutionStateListener listener = mock(JobExecutionStateListener.class);
final JobExecutionState jes1 =
new JobExecutionState(js1, je1, Optional.<JobExecutionStateListener>absent());
assertTransition(jes1, listener, null, RunningState.PENDING, log);
assertTransition(jes1, listener, RunningState.PENDING, RunningState.CANCELLED, log);
// Current state is CANCELLED (final)
assertFailedStateTransition(jes1, RunningState.RUNNING);
assertFailedStateTransition(jes1, RunningState.COMMITTED);
assertFailedStateTransition(jes1, RunningState.SUCCESSFUL);
assertFailedStateTransition(jes1, RunningState.FAILED);
assertFailedStateTransition(jes1, RunningState.RUNNING);
assertFailedStateTransition(jes1, RunningState.CANCELLED);
final JobExecutionState jes2 =
new JobExecutionState(js1, je1, Optional.<JobExecutionStateListener>absent());
assertTransition(jes2, listener, null, RunningState.PENDING, log);
assertTransition(jes2, listener, RunningState.PENDING, RunningState.RUNNING, log);
assertTransition(jes2, listener, RunningState.RUNNING, RunningState.CANCELLED, log);
final JobExecutionState je3 =
new JobExecutionState(js1, je1, Optional.<JobExecutionStateListener>absent());
assertTransition(je3, listener, null, RunningState.PENDING, log);
assertTransition(je3, listener, RunningState.PENDING, RunningState.RUNNING, log);
assertTransition(je3, listener, RunningState.RUNNING, RunningState.SUCCESSFUL, log);
assertTransition(je3, listener, RunningState.SUCCESSFUL, RunningState.CANCELLED, log);
}
private void assertFailedStateTransition(final JobExecutionState jes1, RunningState newState) {
try {
jes1.setRunningState(newState);
Assert.fail("Exception expected");
}
catch (IllegalStateException e) {
// OK
}
}
private void assertTransition(final JobExecutionState jes1,
final JobExecutionStateListener listener,
final RunningState fromState, final RunningState toState,
final Logger log) throws TimeoutException, InterruptedException {
jes1.setRunningState(toState);
Assert.assertEquals(jes1.getRunningState(), toState);
AssertWithBackoff.assertTrue(new Predicate<Void>() {
@Override public boolean apply(Void input) {
try {
verify(listener).onStatusChange(eq(jes1), eq(fromState), eq(toState));
}
catch (Throwable t) {
// ignore
}
return true;
}
}, 50, "expecting state callback", log, 2.0, 10);
}
@Test public void testAwait() throws InterruptedException {
final Logger log = LoggerFactory.getLogger(getClass().getSimpleName() + ".testAwait");
Properties properties = new Properties();
properties.setProperty(JOB_NAME_KEY, "jobname");
JobSpec js1 = JobSpec.builder("gobblin:/testAwaitForDone/job1")
.withConfigAsProperties(properties)
.build();
JobExecution je1 = JobExecutionUpdatable.createFromJobSpec(js1);
final JobExecutionState jes1 =
new JobExecutionState(js1, je1, Optional.<JobExecutionStateListener>absent());
final AtomicBoolean doneDetected = new AtomicBoolean(false);
ThreadFactory doneThreadFactory =
ExecutorsUtils.newDaemonThreadFactory(Optional.of(log), Optional.of("doneDetectionThread"));
Thread doneDetectionThread = doneThreadFactory.newThread(new Runnable() {
@Override public void run() {
try {
jes1.awaitForDone(0);
} catch (InterruptedException | TimeoutException e) {
log.error("Error detected: " + e);
}
doneDetected.set(jes1.getRunningState().isDone());
}
});
doneDetectionThread.start();
long startTime = System.currentTimeMillis();
try {
jes1.awaitForState(RunningState.RUNNING, 10);
Assert.fail("Timeout expected");
} catch (TimeoutException e) {
long now = System.currentTimeMillis();
Assert.assertTrue(now - startTime >= 10, "Insufficient wait: " + (now - startTime));
}
jes1.switchToPending();
jes1.switchToRunning();
try {
jes1.awaitForState(RunningState.RUNNING, 10);
Assert.assertEquals(jes1.getRunningState(), RunningState.RUNNING);
} catch (TimeoutException e) {
Assert.fail("Timeout: ");
}
Assert.assertTrue(doneDetectionThread.isAlive());
jes1.switchToFailed();
doneDetectionThread.join(50);
Assert.assertFalse(doneDetectionThread.isAlive());
Assert.assertTrue(doneDetected.get());
}
}