Add raftVersion field to AppendEntriesReply
[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.ActorRef;
21 import akka.actor.ActorSystem;
22 import akka.actor.PoisonPill;
23 import akka.actor.Props;
24 import akka.testkit.JavaTestKit;
25 import akka.testkit.TestActorRef;
26 import akka.util.Timeout;
27 import java.util.LinkedList;
28 import java.util.List;
29 import java.util.concurrent.TimeUnit;
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, <br/>
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 final ActorSystem system;
49     List<ActorRef> createdActors = new LinkedList<>();
50     Logger LOG = LoggerFactory.getLogger(getClass());
51     private static int actorCount = 1;
52
53     public TestActorFactory(ActorSystem system){
54         this.system = system;
55     }
56
57     /**
58      * Create a normal actor with an auto-generated name
59      *
60      * @param props
61      * @return
62      */
63     public ActorRef createActor(Props props){
64         ActorRef actorRef = system.actorOf(props);
65         return addActor(actorRef);
66     }
67
68     /**
69      * Create a normal actor with the passed in name
70      * @param props
71      * @param actorId name of actor
72      * @return
73      */
74     public ActorRef createActor(Props props, String actorId){
75         ActorRef actorRef = system.actorOf(props, actorId);
76         return addActor(actorRef);
77     }
78
79     /**
80      * Create a test actor with the passed in name
81      * @param props
82      * @param actorId
83      * @param <T>
84      * @return
85      */
86     @SuppressWarnings("unchecked")
87     public <T extends Actor> TestActorRef<T> createTestActor(Props props, String actorId){
88         TestActorRef<T> actorRef = TestActorRef.create(system, props, actorId);
89         return (TestActorRef<T>) addActor(actorRef);
90     }
91
92     private <T extends ActorRef> ActorRef addActor(T actorRef) {
93         createdActors.add(actorRef);
94         verifyActorReady(actorRef);
95         return actorRef;
96     }
97
98     private void verifyActorReady(ActorRef actorRef) {
99         // Sometimes we see messages go to dead letters soon after creation - it seems the actor isn't quite
100         // in a state yet to receive messages or isn't actually created yet. This seems to happen with
101         // actorSelection so, to alleviate it, we use an actorSelection and call resolveOne with retries to
102         // ensure it's ready.
103
104         int tries = 1;
105         while(true) {
106             try {
107                 Timeout timeout = new Timeout(100, TimeUnit.MILLISECONDS);
108                 Future<ActorRef> future = system.actorSelection(actorRef.path()).resolveOne(timeout);
109                 Await.ready(future, timeout.duration());
110                 break;
111             } catch (Exception e) {
112                 if(tries++ > 20) {
113                     throw new RuntimeException(e);
114                 }
115             }
116         }
117     }
118
119     /**
120      * Create a test actor with an auto-generated name
121      * @param props
122      * @param <T>
123      * @return
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);
129     }
130
131     /**
132      * Generate a friendly but unique actor id/name
133      * @param prefix
134      * @return
135      */
136     public String generateActorId(String prefix){
137         return prefix + actorCount++;
138     }
139
140     public void killActor(ActorRef actor, JavaTestKit kit) {
141         killActor(actor, kit, true);
142     }
143
144     public String createTestActorPath(String actorId){
145         return "akka://test/user/" + actorId;
146     }
147
148     private void killActor(ActorRef actor, JavaTestKit kit, boolean remove) {
149         LOG.info("Killing actor {}", actor);
150         kit.watch(actor);
151         actor.tell(PoisonPill.getInstance(), ActorRef.noSender());
152         kit.expectTerminated(JavaTestKit.duration("5 seconds"), actor);
153
154         if(remove) {
155             createdActors.remove(actor);
156         }
157     }
158
159     @Override
160     public void close() {
161         JavaTestKit kit = new JavaTestKit(system);
162         for(ActorRef actor : createdActors) {
163             killActor(actor, kit, false);
164         }
165     }
166 }