Changes for akka 2.4.11
[controller.git] / opendaylight / md-sal / sal-akka-raft / src / test / java / org / opendaylight / controller / cluster / raft / TestActorFactory.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8
9 package org.opendaylight.controller.cluster.raft;
10
11 /*
12  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
13  *
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
17  */
18
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.PoisonPill;
26 import akka.actor.Props;
27 import akka.pattern.Patterns;
28 import akka.testkit.JavaTestKit;
29 import akka.testkit.TestActorRef;
30 import akka.util.Timeout;
31 import com.google.common.base.Stopwatch;
32 import com.google.common.util.concurrent.Uninterruptibles;
33 import java.util.LinkedList;
34 import java.util.List;
35 import java.util.concurrent.TimeUnit;
36 import org.junit.Assert;
37 import org.slf4j.Logger;
38 import org.slf4j.LoggerFactory;
39 import scala.concurrent.Await;
40 import scala.concurrent.Future;
41
42 /**
43  * TestActorFactory provides methods to create both normal and test actors and to kill them when the factory is closed
44  * The ideal usage for TestActorFactory is with try with resources.
45  * <p/>
46  * For example <br/>
47  * <pre>
48  *     try (TestActorFactory factory = new TestActorFactory(getSystem())){
49  *         factory.createActor(props);
50  *         factory.createTestActor(props);
51  *         factory.generateActorId("leader-");
52  *     }
53  * </pre>
54  */
55 public class TestActorFactory implements AutoCloseable {
56     private static final Logger LOG = LoggerFactory.getLogger(TestActorFactory.class);
57
58     private final ActorSystem system;
59     List<ActorRef> createdActors = new LinkedList<>();
60     private static int actorCount = 1;
61
62     public TestActorFactory(ActorSystem system) {
63         this.system = system;
64     }
65
66     /**
67      * Create a normal actor with an auto-generated name.
68      *
69      * @param props the actor Props
70      * @return the ActorRef
71      */
72     public ActorRef createActor(Props props) {
73         ActorRef actorRef = system.actorOf(props);
74         return addActor(actorRef, true);
75     }
76
77     /**
78      * Create a normal actor with the passed in name.
79      *
80      * @param props the actor Props
81      * @param actorId name of actor
82      * @return the ActorRef
83      */
84     public ActorRef createActor(Props props, String actorId) {
85         ActorRef actorRef = system.actorOf(props, actorId);
86         return addActor(actorRef, true);
87     }
88
89     /**
90      * Create a normal actor with the passed in name w/o verifying that the actor is ready.
91      *
92      * @param props the actor Props
93      * @param actorId name of actor
94      * @return the ActorRef
95      */
96     public ActorRef createActorNoVerify(Props props, String actorId) {
97         ActorRef actorRef = system.actorOf(props, actorId);
98         return addActor(actorRef, false);
99     }
100
101     /**
102      * Create a test actor with the passed in name.
103      *
104      * @param props the actor Props
105      * @param actorId name of actor
106      * @param <T> the actor type
107      * @return the ActorRef
108      */
109     @SuppressWarnings("unchecked")
110     public <T extends Actor> TestActorRef<T> createTestActor(Props props, String actorId) {
111         TestActorRef<T> actorRef = TestActorRef.create(system, props, actorId);
112         return (TestActorRef<T>) addActor(actorRef, true);
113     }
114
115     /**
116      * Create a test actor with an auto-generated name.
117      *
118      * @param props the actor Props
119      * @param <T> the actor type
120      * @return the TestActorRef
121      */
122     @SuppressWarnings("unchecked")
123     public <T extends Actor> TestActorRef<T> createTestActor(Props props) {
124         TestActorRef<T> actorRef = TestActorRef.create(system, props);
125         return (TestActorRef<T>) addActor(actorRef, true);
126     }
127
128     private <T extends ActorRef> ActorRef addActor(T actorRef, boolean verify) {
129         createdActors.add(actorRef);
130         if (verify) {
131             verifyActorReady(actorRef);
132         }
133
134         return actorRef;
135     }
136
137     @SuppressWarnings("checkstyle:IllegalCatch")
138     private void verifyActorReady(ActorRef actorRef) {
139         // Sometimes we see messages go to dead letters soon after creation - it seems the actor isn't quite
140         // in a state yet to receive messages or isn't actually created yet. This seems to happen with
141         // actorSelection so, to alleviate it, we use an actorSelection and send an Identify message with
142         // retries to ensure it's ready.
143
144         Timeout timeout = new Timeout(100, TimeUnit.MILLISECONDS);
145         Throwable lastError = null;
146         Stopwatch sw = Stopwatch.createStarted();
147         while (sw.elapsed(TimeUnit.SECONDS) <= 10) {
148             try {
149                 ActorSelection actorSelection = system.actorSelection(actorRef.path().toString());
150                 Future<Object> future = Patterns.ask(actorSelection, new Identify(""), timeout);
151                 ActorIdentity reply = (ActorIdentity)Await.result(future, timeout.duration());
152                 Assert.assertNotNull("Identify returned null", reply.getRef());
153                 return;
154             } catch (Exception | AssertionError e) {
155                 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
156                 lastError = e;
157             }
158         }
159
160         throw new RuntimeException(lastError);
161     }
162
163     /**
164      * Generate a friendly but unique actor id/name.
165      *
166      * @param prefix the name prefix
167      * @return the actor name
168      */
169     public String generateActorId(String prefix) {
170         return prefix + actorCount++;
171     }
172
173     public void killActor(ActorRef actor, JavaTestKit kit) {
174         killActor(actor, kit, true);
175     }
176
177     private void killActor(ActorRef actor, JavaTestKit kit, boolean remove) {
178         LOG.info("Killing actor {}", actor);
179         kit.watch(actor);
180         actor.tell(PoisonPill.getInstance(), ActorRef.noSender());
181         kit.expectTerminated(JavaTestKit.duration("5 seconds"), actor);
182
183         if (remove) {
184             createdActors.remove(actor);
185         }
186     }
187
188     public String createTestActorPath(String actorId) {
189         return "akka://test/user/" + actorId;
190     }
191
192     @Override
193     public void close() {
194         JavaTestKit kit = new JavaTestKit(system);
195         for (ActorRef actor : createdActors) {
196             killActor(actor, kit, false);
197         }
198     }
199 }