Bug 6540: Refactor FollowerToSnapshot to its own class
[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, <br/>
45  * For example <br/>
46  * <pre>
47  *     try (TestActorFactory factory = new TestActorFactory(getSystem())){
48  *         factory.createActor(props);
49  *         factory.createTestActor(props);
50  *         factory.generateActorId("leader-");
51  *     }
52  * </pre>
53  */
54 public class TestActorFactory implements AutoCloseable {
55     private final ActorSystem system;
56     List<ActorRef> createdActors = new LinkedList<>();
57     Logger LOG = LoggerFactory.getLogger(getClass());
58     private static int actorCount = 1;
59
60     public TestActorFactory(ActorSystem system){
61         this.system = system;
62     }
63
64     /**
65      * Create a normal actor with an auto-generated name
66      *
67      * @param props
68      * @return
69      */
70     public ActorRef createActor(Props props){
71         ActorRef actorRef = system.actorOf(props);
72         return addActor(actorRef);
73     }
74
75     /**
76      * Create a normal actor with the passed in name
77      * @param props
78      * @param actorId name of actor
79      * @return
80      */
81     public ActorRef createActor(Props props, String actorId){
82         ActorRef actorRef = system.actorOf(props, actorId);
83         return addActor(actorRef);
84     }
85
86     /**
87      * Create a test actor with the passed in name
88      * @param props
89      * @param actorId
90      * @param <T>
91      * @return
92      */
93     @SuppressWarnings("unchecked")
94     public <T extends Actor> TestActorRef<T> createTestActor(Props props, String actorId){
95         TestActorRef<T> actorRef = TestActorRef.create(system, props, actorId);
96         return (TestActorRef<T>) addActor(actorRef);
97     }
98
99     private <T extends ActorRef> ActorRef addActor(T actorRef) {
100         createdActors.add(actorRef);
101         verifyActorReady(actorRef);
102         return actorRef;
103     }
104
105     private void verifyActorReady(ActorRef actorRef) {
106         // Sometimes we see messages go to dead letters soon after creation - it seems the actor isn't quite
107         // in a state yet to receive messages or isn't actually created yet. This seems to happen with
108         // actorSelection so, to alleviate it, we use an actorSelection and send an Identify message with
109         // retries to ensure it's ready.
110
111         Timeout timeout = new Timeout(100, TimeUnit.MILLISECONDS);
112         Throwable lastError = null;
113         Stopwatch sw = Stopwatch.createStarted();
114         while(sw.elapsed(TimeUnit.SECONDS) <= 10) {
115             try {
116                 ActorSelection actorSelection = system.actorSelection(actorRef.path().toString());
117                 Future<Object> future = Patterns.ask(actorSelection, new Identify(""), timeout);
118                 ActorIdentity reply = (ActorIdentity)Await.result(future, timeout.duration());
119                 Assert.assertNotNull("Identify returned null", reply.getRef());
120                 return;
121             } catch (Exception | AssertionError e) {
122                 Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
123                 lastError = e;
124             }
125         }
126
127         throw new RuntimeException(lastError);
128     }
129
130     /**
131      * Create a test actor with an auto-generated name
132      * @param props
133      * @param <T>
134      * @return
135      */
136     @SuppressWarnings("unchecked")
137     public <T extends Actor> TestActorRef<T> createTestActor(Props props){
138         TestActorRef<T> actorRef = TestActorRef.create(system, props);
139         return (TestActorRef<T>) addActor(actorRef);
140     }
141
142     /**
143      * Generate a friendly but unique actor id/name
144      * @param prefix
145      * @return
146      */
147     public String generateActorId(String prefix){
148         return prefix + actorCount++;
149     }
150
151     public void killActor(ActorRef actor, JavaTestKit kit) {
152         killActor(actor, kit, true);
153     }
154
155     public String createTestActorPath(String actorId){
156         return "akka://test/user/" + actorId;
157     }
158
159     private void killActor(ActorRef actor, JavaTestKit kit, boolean remove) {
160         LOG.info("Killing actor {}", actor);
161         kit.watch(actor);
162         actor.tell(PoisonPill.getInstance(), ActorRef.noSender());
163         kit.expectTerminated(JavaTestKit.duration("5 seconds"), actor);
164
165         if(remove) {
166             createdActors.remove(actor);
167         }
168     }
169
170     @Override
171     public void close() {
172         JavaTestKit kit = new JavaTestKit(system);
173         for(ActorRef actor : createdActors) {
174             killActor(actor, kit, false);
175         }
176     }
177 }