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