package oncue.tests.robustness; import java.util.Arrays; import java.util.HashSet; import org.junit.Test; import akka.actor.Actor; import akka.actor.ActorRef; import akka.actor.Props; import akka.actor.UntypedActorFactory; import akka.testkit.JavaTestKit; import akka.testkit.TestActorRef; import oncue.agent.UnlimitedCapacityAgent; import oncue.common.messages.EnqueueJob; import oncue.common.messages.Job; import oncue.common.messages.JobProgress; import oncue.common.messages.SimpleMessages.SimpleMessage; import oncue.common.messages.WorkResponse; import oncue.tests.base.ActorSystemTest; import oncue.tests.workers.PerpetualTestWorker; import oncue.tests.workers.TestWorker; public class AgentDisconnectsTest extends ActorSystemTest { public AgentDisconnectsTest() { this.waitForRunningJobs = false; } /** * This test simulates an agent failing to send a heartbeat to the Scheduler in the required * amount of time. This causes the scheduler to assume that the agent has died and to give the * job that was in progress to the next compatible agent that comes along. */ @SuppressWarnings("serial") @Test public void testAgentDisconnectsAndReconnects() { new JavaTestKit(system) { { // Create a scheduler probe final JavaTestKit schedulerProbe = new JavaTestKit(system) { { new IgnoreMsg() { @Override protected boolean ignore(Object message) { if (message.equals(SimpleMessage.AGENT_DEAD) || message instanceof WorkResponse) return false; else return true; } }; } }; // Create a scheduler with a probe ActorRef scheduler = createScheduler(system, schedulerProbe.getRef()); // Create an agent probe final JavaTestKit agentProbe = new JavaTestKit(system) { { new IgnoreMsg() { protected boolean ignore(Object message) { return !(message instanceof WorkResponse); } }; } }; // Create a naked agent final Props agentProps = new Props(new UntypedActorFactory() { @Override public Actor create() { UnlimitedCapacityAgent unlimitedCapacityAgent = new UnlimitedCapacityAgent( new HashSet<>(Arrays.asList(PerpetualTestWorker.class.getName(), TestWorker.class.getName()))); unlimitedCapacityAgent.injectProbe(agentProbe.getRef()); return unlimitedCapacityAgent; } }); final TestActorRef<UnlimitedCapacityAgent> agentRef = TestActorRef.create(system, agentProps, settings.AGENT_NAME); // Enqueue a job scheduler.tell(new EnqueueJob(PerpetualTestWorker.class.getName()), getRef()); expectMsgClass(Job.class); // Wait for the agent to be sent the job by the scheduler new AwaitCond(duration("5 seconds")) { @Override protected boolean cond() { WorkResponse response = agentProbe.expectMsgClass(WorkResponse.class); return !response.getJobs().isEmpty(); } }; agentRef.underlyingActor().stopHeartbeat(); schedulerProbe.expectMsgEquals(duration("60 seconds"), SimpleMessage.AGENT_DEAD); agentRef.underlyingActor().startHeartbeat(); // Wait for the scheduler to re-schedule the job agentProbe.expectMsgClass(duration("20 seconds"), WorkResponse.class); // Wait until we receive another JobProgress message. Without the guard in // AbstractAgent#spawnWorker that stops actors from being spawned if the job is in // progress an Exception would be thrown before this JobProgress message is // returned. This AwaitCond is a little odd because we don't have the latest version // of Akka that has the fishForMessage method. schedulerProbe.ignoreNoMsg(); new AwaitCond(duration("5 seconds")) { @Override protected boolean cond() { @SuppressWarnings("unchecked") Object message = schedulerProbe.expectMsgAnyClassOf(JobProgress.class, WorkResponse.class); if (message instanceof WorkResponse) { return false; } return true; } }; } }; } }