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 static org.junit.Assert.assertTrue;
12 import akka.actor.Actor;
13 import akka.actor.ActorIdentity;
14 import akka.actor.ActorRef;
15 import akka.actor.ActorSelection;
16 import akka.actor.ActorSystem;
17 import akka.actor.Identify;
18 import akka.actor.InvalidActorNameException;
19 import akka.actor.PoisonPill;
20 import akka.actor.Props;
21 import akka.pattern.Patterns;
22 import akka.testkit.TestActorRef;
23 import akka.testkit.javadsl.TestKit;
24 import akka.util.Timeout;
25 import com.google.common.base.Stopwatch;
26 import com.google.common.util.concurrent.Uninterruptibles;
27 import java.time.Duration;
28 import java.util.ArrayList;
29 import java.util.List;
30 import java.util.concurrent.TimeUnit;
31 import org.slf4j.Logger;
32 import org.slf4j.LoggerFactory;
33 import scala.concurrent.Await;
34 import scala.concurrent.Future;
37 * TestActorFactory provides methods to create both normal and test actors and to kill them when the factory is closed
38 * The ideal usage for TestActorFactory is with try with resources.
42 * try (TestActorFactory factory = new TestActorFactory(getSystem())){
43 * factory.createActor(props);
44 * factory.createTestActor(props);
45 * factory.generateActorId("leader-");
49 public class TestActorFactory implements AutoCloseable {
50 private static final Logger LOG = LoggerFactory.getLogger(TestActorFactory.class);
52 private final ActorSystem system;
53 private final List<ActorRef> createdActors = new ArrayList<>();
54 private static int actorCount = 1;
56 public TestActorFactory(final ActorSystem system) {
61 * Create a normal actor with an auto-generated name.
63 * @param props the actor Props
64 * @return the ActorRef
66 public ActorRef createActor(final Props props) {
67 ActorRef actorRef = system.actorOf(props);
68 return addActor(actorRef, true);
72 * Create a normal actor with the passed in name.
74 * @param props the actor Props
75 * @param actorId name of actor
76 * @return the ActorRef
78 public ActorRef createActor(final Props props, final String actorId) {
79 ActorRef actorRef = system.actorOf(props, actorId);
80 return addActor(actorRef, true);
84 * Create a normal actor with the passed in name w/o verifying that the actor is ready.
86 * @param props the actor Props
87 * @param actorId name of actor
88 * @return the ActorRef
90 public ActorRef createActorNoVerify(final Props props, final String actorId) {
91 ActorRef actorRef = system.actorOf(props, actorId);
92 return addActor(actorRef, false);
96 * Create a test actor with the passed in name.
98 * @param props the actor Props
99 * @param actorId name of actor
100 * @param <T> the actor type
101 * @return the ActorRef
103 @SuppressWarnings("unchecked")
104 public <T extends Actor> TestActorRef<T> createTestActor(final Props props, final String actorId) {
105 InvalidActorNameException lastError = null;
106 for (int i = 0; i < 10; i++) {
108 TestActorRef<T> actorRef = TestActorRef.create(system, props, actorId);
109 return (TestActorRef<T>) addActor(actorRef, true);
110 } catch (InvalidActorNameException e) {
112 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
120 * Create a test actor with an auto-generated name.
122 * @param props the actor Props
123 * @param <T> the actor type
124 * @return the TestActorRef
126 @SuppressWarnings("unchecked")
127 public <T extends Actor> TestActorRef<T> createTestActor(final Props props) {
128 TestActorRef<T> actorRef = TestActorRef.create(system, props);
129 return (TestActorRef<T>) addActor(actorRef, true);
132 private <T extends ActorRef> ActorRef addActor(final T actorRef, final boolean verify) {
133 createdActors.add(actorRef);
135 verifyActorReady(actorRef);
141 @SuppressWarnings("checkstyle:IllegalCatch")
142 private void verifyActorReady(final ActorRef actorRef) {
143 // Sometimes we see messages go to dead letters soon after creation - it seems the actor isn't quite
144 // in a state yet to receive messages or isn't actually created yet. This seems to happen with
145 // actorSelection so, to alleviate it, we use an actorSelection and send an Identify message with
146 // retries to ensure it's ready.
148 Timeout timeout = new Timeout(100, TimeUnit.MILLISECONDS);
149 Throwable lastError = null;
150 Stopwatch sw = Stopwatch.createStarted();
151 while (sw.elapsed(TimeUnit.SECONDS) <= 10) {
153 ActorSelection actorSelection = system.actorSelection(actorRef.path().toString());
154 Future<Object> future = Patterns.ask(actorSelection, new Identify(""), timeout);
155 ActorIdentity reply = (ActorIdentity)Await.result(future, timeout.duration());
156 assertTrue("Identify returned non-present", reply.getActorRef().isPresent());
158 } catch (Exception | AssertionError e) {
159 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
164 throw new RuntimeException(lastError);
168 * Generate a friendly but unique actor id/name.
170 * @param prefix the name prefix
171 * @return the actor name
173 public String generateActorId(final String prefix) {
174 return prefix + actorCount++;
177 public void killActor(final ActorRef actor, final TestKit kit) {
178 killActor(actor, kit, true);
181 private void killActor(final ActorRef actor, final TestKit kit, final boolean remove) {
182 LOG.info("Killing actor {}", actor);
184 actor.tell(PoisonPill.getInstance(), ActorRef.noSender());
185 kit.expectTerminated(Duration.ofSeconds(5), actor);
188 createdActors.remove(actor);
192 public String createTestActorPath(final String actorId) {
193 return "akka://test/user/" + actorId;
197 public void close() {
198 TestKit kit = new TestKit(system);
199 for (ActorRef actor : createdActors) {
200 killActor(actor, kit, false);