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
8 package org.opendaylight.controller.cluster.raft;
10 import akka.actor.Actor;
11 import akka.actor.ActorIdentity;
12 import akka.actor.ActorRef;
13 import akka.actor.ActorSelection;
14 import akka.actor.ActorSystem;
15 import akka.actor.Identify;
16 import akka.actor.InvalidActorNameException;
17 import akka.actor.PoisonPill;
18 import akka.actor.Props;
19 import akka.pattern.Patterns;
20 import akka.testkit.TestActorRef;
21 import akka.testkit.javadsl.TestKit;
22 import akka.util.Timeout;
23 import com.google.common.base.Stopwatch;
24 import com.google.common.util.concurrent.Uninterruptibles;
25 import java.util.LinkedList;
26 import java.util.List;
27 import java.util.concurrent.TimeUnit;
28 import org.junit.Assert;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31 import scala.concurrent.Await;
32 import scala.concurrent.Future;
35 * TestActorFactory provides methods to create both normal and test actors and to kill them when the factory is closed
36 * The ideal usage for TestActorFactory is with try with resources.
40 * try (TestActorFactory factory = new TestActorFactory(getSystem())){
41 * factory.createActor(props);
42 * factory.createTestActor(props);
43 * factory.generateActorId("leader-");
47 public class TestActorFactory implements AutoCloseable {
48 private static final Logger LOG = LoggerFactory.getLogger(TestActorFactory.class);
50 private final ActorSystem system;
51 List<ActorRef> createdActors = new LinkedList<>();
52 private static int actorCount = 1;
54 public TestActorFactory(ActorSystem system) {
59 * Create a normal actor with an auto-generated name.
61 * @param props the actor Props
62 * @return the ActorRef
64 public ActorRef createActor(Props props) {
65 ActorRef actorRef = system.actorOf(props);
66 return addActor(actorRef, true);
70 * Create a normal actor with the passed in name.
72 * @param props the actor Props
73 * @param actorId name of actor
74 * @return the ActorRef
76 public ActorRef createActor(Props props, String actorId) {
77 ActorRef actorRef = system.actorOf(props, actorId);
78 return addActor(actorRef, true);
82 * Create a normal actor with the passed in name w/o verifying that the actor is ready.
84 * @param props the actor Props
85 * @param actorId name of actor
86 * @return the ActorRef
88 public ActorRef createActorNoVerify(Props props, String actorId) {
89 ActorRef actorRef = system.actorOf(props, actorId);
90 return addActor(actorRef, false);
94 * Create a test actor with the passed in name.
96 * @param props the actor Props
97 * @param actorId name of actor
98 * @param <T> the actor type
99 * @return the ActorRef
101 @SuppressWarnings("unchecked")
102 public <T extends Actor> TestActorRef<T> createTestActor(Props props, String actorId) {
103 InvalidActorNameException lastError = null;
104 for (int i = 0; i < 10; i++) {
106 TestActorRef<T> actorRef = TestActorRef.create(system, props, actorId);
107 return (TestActorRef<T>) addActor(actorRef, true);
108 } catch (InvalidActorNameException e) {
110 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
118 * Create a test actor with an auto-generated name.
120 * @param props the actor Props
121 * @param <T> the actor type
122 * @return the TestActorRef
124 @SuppressWarnings("unchecked")
125 public <T extends Actor> TestActorRef<T> createTestActor(Props props) {
126 TestActorRef<T> actorRef = TestActorRef.create(system, props);
127 return (TestActorRef<T>) addActor(actorRef, true);
130 private <T extends ActorRef> ActorRef addActor(T actorRef, boolean verify) {
131 createdActors.add(actorRef);
133 verifyActorReady(actorRef);
139 @SuppressWarnings("checkstyle:IllegalCatch")
140 private void verifyActorReady(ActorRef actorRef) {
141 // Sometimes we see messages go to dead letters soon after creation - it seems the actor isn't quite
142 // in a state yet to receive messages or isn't actually created yet. This seems to happen with
143 // actorSelection so, to alleviate it, we use an actorSelection and send an Identify message with
144 // retries to ensure it's ready.
146 Timeout timeout = new Timeout(100, TimeUnit.MILLISECONDS);
147 Throwable lastError = null;
148 Stopwatch sw = Stopwatch.createStarted();
149 while (sw.elapsed(TimeUnit.SECONDS) <= 10) {
151 ActorSelection actorSelection = system.actorSelection(actorRef.path().toString());
152 Future<Object> future = Patterns.ask(actorSelection, new Identify(""), timeout);
153 ActorIdentity reply = (ActorIdentity)Await.result(future, timeout.duration());
154 Assert.assertTrue("Identify returned non-present", reply.getActorRef().isPresent());
156 } catch (Exception | AssertionError e) {
157 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
162 throw new RuntimeException(lastError);
166 * Generate a friendly but unique actor id/name.
168 * @param prefix the name prefix
169 * @return the actor name
171 public String generateActorId(String prefix) {
172 return prefix + actorCount++;
175 public void killActor(ActorRef actor, TestKit kit) {
176 killActor(actor, kit, true);
179 private void killActor(ActorRef actor, TestKit kit, boolean remove) {
180 LOG.info("Killing actor {}", actor);
182 actor.tell(PoisonPill.getInstance(), ActorRef.noSender());
183 kit.expectTerminated(kit.duration("5 seconds"), actor);
186 createdActors.remove(actor);
190 public String createTestActorPath(String actorId) {
191 return "akka://test/user/" + actorId;
195 public void close() {
196 TestKit kit = new TestKit(system);
197 for (ActorRef actor : createdActors) {
198 killActor(actor, kit, false);