c71c8b7de4fd8f204aa7667eea5103644527d0d5
[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 package org.opendaylight.controller.cluster.raft;
9
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;
33
34 /**
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.
37  * <p/>
38  * For example <br/>
39  * <pre>
40  *     try (TestActorFactory factory = new TestActorFactory(getSystem())){
41  *         factory.createActor(props);
42  *         factory.createTestActor(props);
43  *         factory.generateActorId("leader-");
44  *     }
45  * </pre>
46  */
47 public class TestActorFactory implements AutoCloseable {
48     private static final Logger LOG = LoggerFactory.getLogger(TestActorFactory.class);
49
50     private final ActorSystem system;
51     List<ActorRef> createdActors = new LinkedList<>();
52     private static int actorCount = 1;
53
54     public TestActorFactory(ActorSystem system) {
55         this.system = system;
56     }
57
58     /**
59      * Create a normal actor with an auto-generated name.
60      *
61      * @param props the actor Props
62      * @return the ActorRef
63      */
64     public ActorRef createActor(Props props) {
65         ActorRef actorRef = system.actorOf(props);
66         return addActor(actorRef, true);
67     }
68
69     /**
70      * Create a normal actor with the passed in name.
71      *
72      * @param props the actor Props
73      * @param actorId name of actor
74      * @return the ActorRef
75      */
76     public ActorRef createActor(Props props, String actorId) {
77         ActorRef actorRef = system.actorOf(props, actorId);
78         return addActor(actorRef, true);
79     }
80
81     /**
82      * Create a normal actor with the passed in name w/o verifying that the actor is ready.
83      *
84      * @param props the actor Props
85      * @param actorId name of actor
86      * @return the ActorRef
87      */
88     public ActorRef createActorNoVerify(Props props, String actorId) {
89         ActorRef actorRef = system.actorOf(props, actorId);
90         return addActor(actorRef, false);
91     }
92
93     /**
94      * Create a test actor with the passed in name.
95      *
96      * @param props the actor Props
97      * @param actorId name of actor
98      * @param <T> the actor type
99      * @return the ActorRef
100      */
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++) {
105             try {
106                 TestActorRef<T> actorRef = TestActorRef.create(system, props, actorId);
107                 return (TestActorRef<T>) addActor(actorRef, true);
108             } catch (InvalidActorNameException e) {
109                 lastError = e;
110                 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
111             }
112         }
113
114         throw lastError;
115     }
116
117     /**
118      * Create a test actor with an auto-generated name.
119      *
120      * @param props the actor Props
121      * @param <T> the actor type
122      * @return the TestActorRef
123      */
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);
128     }
129
130     private <T extends ActorRef> ActorRef addActor(T actorRef, boolean verify) {
131         createdActors.add(actorRef);
132         if (verify) {
133             verifyActorReady(actorRef);
134         }
135
136         return actorRef;
137     }
138
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.
145
146         Timeout timeout = new Timeout(100, TimeUnit.MILLISECONDS);
147         Throwable lastError = null;
148         Stopwatch sw = Stopwatch.createStarted();
149         while (sw.elapsed(TimeUnit.SECONDS) <= 10) {
150             try {
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());
155                 return;
156             } catch (Exception | AssertionError e) {
157                 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
158                 lastError = e;
159             }
160         }
161
162         throw new RuntimeException(lastError);
163     }
164
165     /**
166      * Generate a friendly but unique actor id/name.
167      *
168      * @param prefix the name prefix
169      * @return the actor name
170      */
171     public String generateActorId(String prefix) {
172         return prefix + actorCount++;
173     }
174
175     public void killActor(ActorRef actor, TestKit kit) {
176         killActor(actor, kit, true);
177     }
178
179     private void killActor(ActorRef actor, TestKit kit, boolean remove) {
180         LOG.info("Killing actor {}", actor);
181         kit.watch(actor);
182         actor.tell(PoisonPill.getInstance(), ActorRef.noSender());
183         kit.expectTerminated(kit.duration("5 seconds"), actor);
184
185         if (remove) {
186             createdActors.remove(actor);
187         }
188     }
189
190     public String createTestActorPath(String actorId) {
191         return "akka://test/user/" + actorId;
192     }
193
194     @Override
195     public void close() {
196         TestKit kit = new TestKit(system);
197         for (ActorRef actor : createdActors) {
198             killActor(actor, kit, false);
199         }
200     }
201 }