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;
11 import akka.actor.Actor;
12 import akka.actor.ActorIdentity;
13 import akka.actor.ActorRef;
14 import akka.actor.ActorSelection;
15 import akka.actor.ActorSystem;
16 import akka.actor.Identify;
17 import akka.actor.InvalidActorNameException;
18 import akka.actor.PoisonPill;
19 import akka.actor.Props;
20 import akka.pattern.Patterns;
21 import akka.testkit.TestActorRef;
22 import akka.testkit.javadsl.TestKit;
23 import akka.util.Timeout;
24 import com.google.common.base.Stopwatch;
25 import com.google.common.util.concurrent.Uninterruptibles;
26 import java.util.LinkedList;
27 import java.util.List;
28 import java.util.concurrent.TimeUnit;
29 import org.junit.Assert;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
32 import scala.concurrent.Await;
33 import scala.concurrent.Future;
36 * TestActorFactory provides methods to create both normal and test actors and to kill them when the factory is closed
37 * The ideal usage for TestActorFactory is with try with resources.
41 * try (TestActorFactory factory = new TestActorFactory(getSystem())){
42 * factory.createActor(props);
43 * factory.createTestActor(props);
44 * factory.generateActorId("leader-");
48 public class TestActorFactory implements AutoCloseable {
49 private static final Logger LOG = LoggerFactory.getLogger(TestActorFactory.class);
51 private final ActorSystem system;
52 List<ActorRef> createdActors = new LinkedList<>();
53 private static int actorCount = 1;
55 public TestActorFactory(ActorSystem system) {
60 * Create a normal actor with an auto-generated name.
62 * @param props the actor Props
63 * @return the ActorRef
65 public ActorRef createActor(Props props) {
66 ActorRef actorRef = system.actorOf(props);
67 return addActor(actorRef, true);
71 * Create a normal actor with the passed in name.
73 * @param props the actor Props
74 * @param actorId name of actor
75 * @return the ActorRef
77 public ActorRef createActor(Props props, String actorId) {
78 ActorRef actorRef = system.actorOf(props, actorId);
79 return addActor(actorRef, true);
83 * Create a normal actor with the passed in name w/o verifying that the actor is ready.
85 * @param props the actor Props
86 * @param actorId name of actor
87 * @return the ActorRef
89 public ActorRef createActorNoVerify(Props props, String actorId) {
90 ActorRef actorRef = system.actorOf(props, actorId);
91 return addActor(actorRef, false);
95 * Create a test actor with the passed in name.
97 * @param props the actor Props
98 * @param actorId name of actor
99 * @param <T> the actor type
100 * @return the ActorRef
102 @SuppressWarnings("unchecked")
103 public <T extends Actor> TestActorRef<T> createTestActor(Props props, String actorId) {
104 InvalidActorNameException lastError = null;
105 for (int i = 0; i < 10; i++) {
107 TestActorRef<T> actorRef = TestActorRef.create(system, props, actorId);
108 return (TestActorRef<T>) addActor(actorRef, true);
109 } catch (InvalidActorNameException e) {
111 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
119 * Create a test actor with an auto-generated name.
121 * @param props the actor Props
122 * @param <T> the actor type
123 * @return the TestActorRef
125 @SuppressWarnings("unchecked")
126 public <T extends Actor> TestActorRef<T> createTestActor(Props props) {
127 TestActorRef<T> actorRef = TestActorRef.create(system, props);
128 return (TestActorRef<T>) addActor(actorRef, true);
131 private <T extends ActorRef> ActorRef addActor(T actorRef, boolean verify) {
132 createdActors.add(actorRef);
134 verifyActorReady(actorRef);
140 @SuppressWarnings("checkstyle:IllegalCatch")
141 private void verifyActorReady(ActorRef actorRef) {
142 // Sometimes we see messages go to dead letters soon after creation - it seems the actor isn't quite
143 // in a state yet to receive messages or isn't actually created yet. This seems to happen with
144 // actorSelection so, to alleviate it, we use an actorSelection and send an Identify message with
145 // retries to ensure it's ready.
147 Timeout timeout = new Timeout(100, TimeUnit.MILLISECONDS);
148 Throwable lastError = null;
149 Stopwatch sw = Stopwatch.createStarted();
150 while (sw.elapsed(TimeUnit.SECONDS) <= 10) {
152 ActorSelection actorSelection = system.actorSelection(actorRef.path().toString());
153 Future<Object> future = Patterns.ask(actorSelection, new Identify(""), timeout);
154 ActorIdentity reply = (ActorIdentity)Await.result(future, timeout.duration());
155 Assert.assertNotNull("Identify returned null", reply.getRef());
157 } catch (Exception | AssertionError e) {
158 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
163 throw new RuntimeException(lastError);
167 * Generate a friendly but unique actor id/name.
169 * @param prefix the name prefix
170 * @return the actor name
172 public String generateActorId(String prefix) {
173 return prefix + actorCount++;
176 public void killActor(ActorRef actor, TestKit kit) {
177 killActor(actor, kit, true);
180 private void killActor(ActorRef actor, TestKit kit, boolean remove) {
181 LOG.info("Killing actor {}", actor);
183 actor.tell(PoisonPill.getInstance(), ActorRef.noSender());
184 kit.expectTerminated(kit.duration("5 seconds"), actor);
187 createdActors.remove(actor);
191 public String createTestActorPath(String actorId) {
192 return "akka://test/user/" + actorId;
196 public void close() {
197 TestKit kit = new TestKit(system);
198 for (ActorRef actor : createdActors) {
199 killActor(actor, kit, false);