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