2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.controller.cluster.raft;
12 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
14 * This program and the accompanying materials are made available under the
15 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
16 * and is available at http://www.eclipse.org/legal/epl-v10.html
19 import akka.actor.Actor;
20 import akka.actor.ActorIdentity;
21 import akka.actor.ActorRef;
22 import akka.actor.ActorSelection;
23 import akka.actor.ActorSystem;
24 import akka.actor.Identify;
25 import akka.actor.InvalidActorNameException;
26 import akka.actor.PoisonPill;
27 import akka.actor.Props;
28 import akka.pattern.Patterns;
29 import akka.testkit.JavaTestKit;
30 import akka.testkit.TestActorRef;
31 import akka.util.Timeout;
32 import com.google.common.base.Stopwatch;
33 import com.google.common.util.concurrent.Uninterruptibles;
34 import java.util.LinkedList;
35 import java.util.List;
36 import java.util.concurrent.TimeUnit;
37 import org.junit.Assert;
38 import org.slf4j.Logger;
39 import org.slf4j.LoggerFactory;
40 import scala.concurrent.Await;
41 import scala.concurrent.Future;
44 * TestActorFactory provides methods to create both normal and test actors and to kill them when the factory is closed
45 * The ideal usage for TestActorFactory is with try with resources.
49 * try (TestActorFactory factory = new TestActorFactory(getSystem())){
50 * factory.createActor(props);
51 * factory.createTestActor(props);
52 * factory.generateActorId("leader-");
56 public class TestActorFactory implements AutoCloseable {
57 private static final Logger LOG = LoggerFactory.getLogger(TestActorFactory.class);
59 private final ActorSystem system;
60 List<ActorRef> createdActors = new LinkedList<>();
61 private static int actorCount = 1;
63 public TestActorFactory(ActorSystem system) {
68 * Create a normal actor with an auto-generated name.
70 * @param props the actor Props
71 * @return the ActorRef
73 public ActorRef createActor(Props props) {
74 ActorRef actorRef = system.actorOf(props);
75 return addActor(actorRef, true);
79 * Create a normal actor with the passed in name.
81 * @param props the actor Props
82 * @param actorId name of actor
83 * @return the ActorRef
85 public ActorRef createActor(Props props, String actorId) {
86 ActorRef actorRef = system.actorOf(props, actorId);
87 return addActor(actorRef, true);
91 * Create a normal actor with the passed in name w/o verifying that the actor is ready.
93 * @param props the actor Props
94 * @param actorId name of actor
95 * @return the ActorRef
97 public ActorRef createActorNoVerify(Props props, String actorId) {
98 ActorRef actorRef = system.actorOf(props, actorId);
99 return addActor(actorRef, false);
103 * Create a test actor with the passed in name.
105 * @param props the actor Props
106 * @param actorId name of actor
107 * @param <T> the actor type
108 * @return the ActorRef
110 @SuppressWarnings("unchecked")
111 public <T extends Actor> TestActorRef<T> createTestActor(Props props, String actorId) {
112 InvalidActorNameException lastError = null;
113 for (int i = 0; i < 10; i++) {
115 TestActorRef<T> actorRef = TestActorRef.create(system, props, actorId);
116 return (TestActorRef<T>) addActor(actorRef, true);
117 } catch (InvalidActorNameException e) {
119 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
127 * Create a test actor with an auto-generated name.
129 * @param props the actor Props
130 * @param <T> the actor type
131 * @return the TestActorRef
133 @SuppressWarnings("unchecked")
134 public <T extends Actor> TestActorRef<T> createTestActor(Props props) {
135 TestActorRef<T> actorRef = TestActorRef.create(system, props);
136 return (TestActorRef<T>) addActor(actorRef, true);
139 private <T extends ActorRef> ActorRef addActor(T actorRef, boolean verify) {
140 createdActors.add(actorRef);
142 verifyActorReady(actorRef);
148 @SuppressWarnings("checkstyle:IllegalCatch")
149 private void verifyActorReady(ActorRef actorRef) {
150 // Sometimes we see messages go to dead letters soon after creation - it seems the actor isn't quite
151 // in a state yet to receive messages or isn't actually created yet. This seems to happen with
152 // actorSelection so, to alleviate it, we use an actorSelection and send an Identify message with
153 // retries to ensure it's ready.
155 Timeout timeout = new Timeout(100, TimeUnit.MILLISECONDS);
156 Throwable lastError = null;
157 Stopwatch sw = Stopwatch.createStarted();
158 while (sw.elapsed(TimeUnit.SECONDS) <= 10) {
160 ActorSelection actorSelection = system.actorSelection(actorRef.path().toString());
161 Future<Object> future = Patterns.ask(actorSelection, new Identify(""), timeout);
162 ActorIdentity reply = (ActorIdentity)Await.result(future, timeout.duration());
163 Assert.assertNotNull("Identify returned null", reply.getRef());
165 } catch (Exception | AssertionError e) {
166 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
171 throw new RuntimeException(lastError);
175 * Generate a friendly but unique actor id/name.
177 * @param prefix the name prefix
178 * @return the actor name
180 public String generateActorId(String prefix) {
181 return prefix + actorCount++;
184 public void killActor(ActorRef actor, JavaTestKit kit) {
185 killActor(actor, kit, true);
188 private void killActor(ActorRef actor, JavaTestKit kit, boolean remove) {
189 LOG.info("Killing actor {}", actor);
191 actor.tell(PoisonPill.getInstance(), ActorRef.noSender());
192 kit.expectTerminated(JavaTestKit.duration("5 seconds"), actor);
195 createdActors.remove(actor);
199 public String createTestActorPath(String actorId) {
200 return "akka://test/user/" + actorId;
204 public void close() {
205 JavaTestKit kit = new JavaTestKit(system);
206 for (ActorRef actor : createdActors) {
207 killActor(actor, kit, false);