/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.calcnode;
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertNotNull;
import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertTrue;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.Assert;
import org.testng.annotations.Test;
import org.threeten.bp.Instant;
import com.opengamma.engine.cache.CacheSelectHint;
import com.opengamma.engine.calcnode.AbstractJobInvoker;
import com.opengamma.engine.calcnode.CalculationJob;
import com.opengamma.engine.calcnode.CalculationJobItem;
import com.opengamma.engine.calcnode.CalculationJobResult;
import com.opengamma.engine.calcnode.CalculationJobResultItem;
import com.opengamma.engine.calcnode.CalculationJobSpecification;
import com.opengamma.engine.calcnode.Capability;
import com.opengamma.engine.calcnode.JobDispatcher;
import com.opengamma.engine.calcnode.JobInvocationReceiver;
import com.opengamma.engine.calcnode.JobInvoker;
import com.opengamma.engine.calcnode.JobInvokerRegister;
import com.opengamma.id.UniqueId;
import com.opengamma.id.VersionCorrection;
import com.opengamma.util.async.Cancelable;
import com.opengamma.util.test.TestGroup;
import com.opengamma.util.test.Timeout;
/**
*
*/
@Test(groups = TestGroup.INTEGRATION)
public class JobDispatcherTest {
private static final Logger s_logger = LoggerFactory.getLogger(JobDispatcherTest.class);
private static final long TIMEOUT = Timeout.standardTimeoutMillis ();
private final ExecutorService _executorService = Executors.newCachedThreadPool();
private static final AtomicLong s_jobId = new AtomicLong();
protected static CalculationJobSpecification createTestJobSpec() {
return new CalculationJobSpecification(UniqueId.of("Test", "ViewCycle"), "default", Instant.now(), s_jobId.incrementAndGet());
}
protected static List<CalculationJobItem> createTestJobItems() {
return Collections.emptyList();
}
protected static CalculationJob createTestJob() {
return new CalculationJob(createTestJobSpec(), 0L, VersionCorrection.LATEST, null, createTestJobItems(), CacheSelectHint.allPrivate());
}
protected static CalculationJobResult createTestJobResult(final CalculationJobSpecification jobSpec, final long time, final String nodeId) {
return new CalculationJobResult(jobSpec, time, new ArrayList<CalculationJobResultItem>(), nodeId);
}
private class TestJobInvoker extends AbstractJobInvoker {
private JobInvokerRegister _callback;
private boolean _disabled;
public TestJobInvoker(final String nodeId) {
super (nodeId);
}
@Override
public boolean invoke(final CalculationJob job, final JobInvocationReceiver receiver) {
if (_disabled) {
return false;
}
_executorService.execute(new Runnable() {
@Override
public void run() {
receiver.jobCompleted(createTestJobResult(job.getSpecification(), 0, getInvokerId ()));
}
});
return true;
}
@Override
public boolean notifyWhenAvailable(JobInvokerRegister callback) {
_callback = callback;
return false;
}
}
@Test
public void registerInvokerWithJobPending() {
s_logger.info("registerInvokerWithJobPending");
final JobDispatcher jobDispatcher = new JobDispatcher();
final TestJobResultReceiver result = new TestJobResultReceiver();
final CalculationJob job = createTestJob();
jobDispatcher.dispatchJob(job, result);
assertNull(result.getResult());
final TestJobInvoker jobInvoker = new TestJobInvoker("Test");
jobDispatcher.registerJobInvoker(jobInvoker);
final CalculationJobResult jobResult = result.waitForResult(TIMEOUT);
assertNotNull(jobResult);
assertEquals(job.getSpecification(), jobResult.getSpecification());
assertNull(jobInvoker._callback);
}
@Test
public void registerInvokerWithEmptyQueue() {
s_logger.info("registerInvokerWithEmptyQueue");
final JobDispatcher jobDispatcher = new JobDispatcher();
final TestJobInvoker jobInvoker = new TestJobInvoker("Test");
jobDispatcher.registerJobInvoker(jobInvoker);
final TestJobResultReceiver result = new TestJobResultReceiver();
final CalculationJob job = createTestJob();
jobDispatcher.dispatchJob(job, result);
final CalculationJobResult jobResult = result.waitForResult(TIMEOUT);
assertNotNull(jobResult);
assertEquals(job.getSpecification(), jobResult.getSpecification());
assertNull(jobInvoker._callback);
}
private void nodeTest(final String expectedNodeId, final JobDispatcher jobDispatcher) {
final TestJobResultReceiver result = new TestJobResultReceiver();
final CalculationJob job = createTestJob();
jobDispatcher.dispatchJob(job, result);
final CalculationJobResult jobResult = result.waitForResult(TIMEOUT);
assertNotNull(jobResult);
assertEquals(job.getSpecification(), jobResult.getSpecification());
assertEquals(expectedNodeId, jobResult.getComputeNodeId());
}
@Test
public void invokeInRoundRobinOrder() {
s_logger.info("invokeInRoundRobinOrder");
final JobDispatcher jobDispatcher = new JobDispatcher();
final TestJobInvoker node1 = new TestJobInvoker("1");
final TestJobInvoker node2 = new TestJobInvoker("2");
final TestJobInvoker node3 = new TestJobInvoker("3");
jobDispatcher.registerJobInvoker(node1);
jobDispatcher.registerJobInvoker(node2);
jobDispatcher.registerJobInvoker(node3);
nodeTest("1", jobDispatcher);
assertNull(node1._callback);
nodeTest("2", jobDispatcher);
assertNull(node2._callback);
nodeTest("3", jobDispatcher);
assertNull(node3._callback);
node1._disabled = true;
nodeTest("2", jobDispatcher);
assertNotNull(node1._callback);
assertNull(node2._callback);
}
@Test
public void saturateInvokers() {
s_logger.info("saturateInvokers");
final JobDispatcher jobDispatcher = new JobDispatcher();
final JobInvoker[] jobInvokers = new JobInvoker[3];
for (int i = 0; i < jobInvokers.length; i++) {
jobDispatcher.registerJobInvoker(new AbstractJobInvoker("" + (i + 1)) {
private final Random rnd = new Random();
private boolean _busy;
private JobInvokerRegister _callback;
@Override
public boolean invoke(final CalculationJob job, final JobInvocationReceiver receiver) {
final JobInvoker instance = this;
synchronized (instance) {
if (_busy) {
return false;
}
_executorService.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(rnd.nextInt(50));
} catch (InterruptedException e) {
s_logger.warn("invoker {} interrupted", getInvokerId ());
}
s_logger.debug("invoker {} completed job {}", getInvokerId (), job.getSpecification());
receiver.jobCompleted(createTestJobResult(job.getSpecification(), 0L, instance.toString()));
synchronized (instance) {
_busy = false;
if (_callback != null) {
s_logger.debug("re-registering invoker {} with dispatcher", getInvokerId ());
final JobInvokerRegister callback = _callback;
_callback = null;
callback.registerJobInvoker(instance);
} else {
s_logger.debug("invoker {} completed job without notify", getInvokerId ());
}
}
}
});
_busy = true;
return true;
}
}
@Override
public boolean notifyWhenAvailable(final JobInvokerRegister callback) {
synchronized (this) {
if (_busy) {
assertNull(_callback);
s_logger.debug("invoker {} busy - storing callback", getInvokerId ());
_callback = callback;
return false;
} else {
s_logger.debug("invoker {} ready - immediate callback", getInvokerId ());
return true;
}
}
}
});
}
final CalculationJob[] jobs = new CalculationJob[100];
final TestJobResultReceiver[] resultReceivers = new TestJobResultReceiver[jobs.length];
s_logger.debug("Dispatching {} jobs to {} nodes", jobs.length, jobInvokers.length);
for (int i = 0; i < jobs.length; i++) {
jobDispatcher.dispatchJob(jobs[i] = createTestJob(), resultReceivers[i] = new TestJobResultReceiver());
}
s_logger.debug("Jobs dispatched");
for (int i = 0; i < jobs.length; i++) {
s_logger.debug("Waiting for result {}", i);
final CalculationJobResult result = resultReceivers[i].waitForResult(TIMEOUT * 2);
assertNotNull(result);
assertEquals(jobs[i].getSpecification(), result.getSpecification());
}
s_logger.debug("All jobs completed");
}
private class FailingJobInvoker extends AbstractJobInvoker {
private int _failureCount;
public FailingJobInvoker () {
super ("Failing");
}
@Override
public boolean invoke(final CalculationJob job, final JobInvocationReceiver receiver) {
_executorService.execute(new Runnable() {
@Override
public void run() {
s_logger.debug("Failing job {}", job.getSpecification());
_failureCount++;
receiver.jobFailed(FailingJobInvoker.this, "Fail", null);
}
});
return true;
}
@Override
public boolean notifyWhenAvailable(final JobInvokerRegister callback) {
// shouldn't get called
Assert.fail();
return true;
}
}
@Test
public void testJobRetry_failure() {
s_logger.info("testJobRetry_failure");
final JobDispatcher jobDispatcher = new JobDispatcher();
final TestJobResultReceiver result = new TestJobResultReceiver();
final FailingJobInvoker failingInvoker = new FailingJobInvoker();
jobDispatcher.registerJobInvoker(failingInvoker);
final CalculationJob job = createTestJob();
jobDispatcher.dispatchJob(job, result);
CalculationJobResult jobResult = result.waitForResult(TIMEOUT);
assertNotNull(jobResult);
assertEquals(2, failingInvoker._failureCount); // once failed twice at one node, job is aborted
assertEquals(JobDispatcher.DEFAULT_JOB_FAILURE_NODE_ID, jobResult.getComputeNodeId());
}
@Test
public void testJobRetry_success() {
s_logger.info("testJobRetry_sucess");
final JobDispatcher jobDispatcher = new JobDispatcher();
final TestJobResultReceiver result = new TestJobResultReceiver();
final FailingJobInvoker failingInvoker = new FailingJobInvoker();
final TestJobInvoker workingInvoker = new TestJobInvoker("Test");
jobDispatcher.registerJobInvoker(failingInvoker);
jobDispatcher.registerJobInvoker(workingInvoker);
final CalculationJob job = createTestJob();
jobDispatcher.dispatchJob(job, result);
CalculationJobResult jobResult = result.waitForResult(TIMEOUT);
assertNotNull(jobResult);
assertEquals(1, failingInvoker._failureCount); // fail once at this node, then retried on another
assertEquals("Test", jobResult.getComputeNodeId());
assertEquals(job.getSpecification(), jobResult.getSpecification());
}
private class BlockingJobInvoker extends AbstractJobInvoker {
private final long _waitFor;
private AtomicInteger _isAlive;
private boolean _cancelled;
private BlockingJobInvoker(final long waitFor) {
super ("blocking");
_waitFor = waitFor;
}
@Override
public Collection<Capability> getCapabilities() {
return Collections.emptySet();
}
@Override
public boolean invoke(final CalculationJob job, final JobInvocationReceiver receiver) {
_executorService.execute(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(_waitFor);
} catch (InterruptedException e) {
}
receiver.jobCompleted(createTestJobResult(job.getSpecification(), 0, getInvokerId ()));
}
});
return true;
}
@Override
public boolean isAlive (final Collection<CalculationJobSpecification> jobs) {
if (_isAlive != null) {
_isAlive.incrementAndGet ();
return true;
} else {
return false;
}
}
@Override
public void cancel(final Collection<CalculationJobSpecification> jobs) {
_cancelled = true;
}
public boolean isCancelled() {
return _cancelled;
}
@Override
public boolean notifyWhenAvailable(JobInvokerRegister callback) {
// Shouldn't get called
Assert.fail ();
return false;
}
}
@Test(invocationCount = 5, successPercentage = 19)
public void testJobTimeoutFailure() {
s_logger.info("testJobTimeoutFailure");
final JobDispatcher jobDispatcher = new JobDispatcher();
jobDispatcher.setMaxJobExecutionTime(TIMEOUT);
jobDispatcher.setMaxJobAttempts(1);
final TestJobResultReceiver result = new TestJobResultReceiver();
jobDispatcher.dispatchJob(createTestJob(), result);
assertNull(result.getResult());
final BlockingJobInvoker blockingInvoker = new BlockingJobInvoker(2 * TIMEOUT);
jobDispatcher.registerJobInvoker(blockingInvoker);
CalculationJobResult jobResult = result.waitForResult(2 * TIMEOUT);
assertNotNull(jobResult);
assertEquals(jobDispatcher.getJobFailureNodeId(), jobResult.getComputeNodeId());
}
@Test(invocationCount = 5, successPercentage = 19)
public void testJobTimeoutSuccess() {
s_logger.info("testJobTimeoutSuccess");
final JobDispatcher jobDispatcher = new JobDispatcher();
jobDispatcher.setMaxJobExecutionTime(3 * TIMEOUT);
jobDispatcher.setMaxJobAttempts(1);
final TestJobResultReceiver result = new TestJobResultReceiver();
jobDispatcher.dispatchJob(createTestJob(), result);
assertNull(result.getResult());
final BlockingJobInvoker blockingInvoker = new BlockingJobInvoker(TIMEOUT);
jobDispatcher.registerJobInvoker(blockingInvoker);
CalculationJobResult jobResult = result.waitForResult(2 * TIMEOUT);
assertNotNull(jobResult);
assertEquals(blockingInvoker.getInvokerId (), jobResult.getComputeNodeId());
}
@Test(invocationCount = 5, successPercentage = 19)
public void testJobTimeoutQuerySuccess () {
s_logger.info("testJobTimeoutQuerySuccess");
final JobDispatcher jobDispatcher = new JobDispatcher();
jobDispatcher.setMaxJobExecutionTime(4 * TIMEOUT);
jobDispatcher.setMaxJobExecutionTimeQuery(TIMEOUT);
jobDispatcher.setMaxJobAttempts(1);
final TestJobResultReceiver result = new TestJobResultReceiver();
jobDispatcher.dispatchJob(createTestJob(), result);
assertNull(result.getResult());
final BlockingJobInvoker blockingInvoker = new BlockingJobInvoker(2 * TIMEOUT);
blockingInvoker._isAlive = new AtomicInteger ();
jobDispatcher.registerJobInvoker(blockingInvoker);
CalculationJobResult jobResult = result.waitForResult(3 * TIMEOUT);
assertNotNull(jobResult);
assertEquals(blockingInvoker.getInvokerId (), jobResult.getComputeNodeId());
assertTrue (blockingInvoker._isAlive.get () > 0);
}
@Test(invocationCount = 5, successPercentage = 19)
public void testJobTimeoutQueryFailure () {
s_logger.info("testJobTimeoutQueryFailure");
final JobDispatcher jobDispatcher = new JobDispatcher();
jobDispatcher.setMaxJobExecutionTime(3 * TIMEOUT);
jobDispatcher.setMaxJobExecutionTimeQuery(TIMEOUT);
jobDispatcher.setMaxJobAttempts(1);
final TestJobResultReceiver result = new TestJobResultReceiver();
jobDispatcher.dispatchJob(createTestJob(), result);
assertNull(result.getResult());
final BlockingJobInvoker blockingInvoker = new BlockingJobInvoker(2 * TIMEOUT);
jobDispatcher.registerJobInvoker(blockingInvoker);
CalculationJobResult jobResult = result.waitForResult(3 * TIMEOUT);
assertNotNull(jobResult);
assertEquals(jobDispatcher.getJobFailureNodeId(), jobResult.getComputeNodeId());
}
@Test(invocationCount = 5, successPercentage = 19)
public void testJobCancel() {
s_logger.info("testJobCancel");
final JobDispatcher jobDispatcher = new JobDispatcher();
jobDispatcher.setMaxJobExecutionTime(2 * TIMEOUT);
jobDispatcher.setMaxJobAttempts(1);
final TestJobResultReceiver result = new TestJobResultReceiver();
Cancelable job = jobDispatcher.dispatchJob(createTestJob(), result);
assertNotNull(job);
assertNull(result.getResult());
final BlockingJobInvoker blockingInvoker = new BlockingJobInvoker(TIMEOUT);
jobDispatcher.registerJobInvoker(blockingInvoker);
assertTrue (job.cancel (false));
assertTrue (blockingInvoker.isCancelled ());
}
}