Fix intermittent failures in FollowerTest
[controller.git] / opendaylight / md-sal / sal-akka-raft / src / test / java / org / opendaylight / controller / cluster / raft / TestActorFactory.java
index 79df92b7d4d64280f9795dfa64e0e0cd507d8fc1..639436d026c04c5c96169b3fc330a120484896ba 100644 (file)
@@ -17,16 +17,24 @@ package org.opendaylight.controller.cluster.raft;
  */
 
 import akka.actor.Actor;
+import akka.actor.ActorIdentity;
 import akka.actor.ActorRef;
+import akka.actor.ActorSelection;
 import akka.actor.ActorSystem;
+import akka.actor.Identify;
+import akka.actor.InvalidActorNameException;
 import akka.actor.PoisonPill;
 import akka.actor.Props;
+import akka.pattern.Patterns;
 import akka.testkit.JavaTestKit;
 import akka.testkit.TestActorRef;
 import akka.util.Timeout;
+import com.google.common.base.Stopwatch;
+import com.google.common.util.concurrent.Uninterruptibles;
 import java.util.LinkedList;
 import java.util.List;
 import java.util.concurrent.TimeUnit;
+import org.junit.Assert;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import scala.concurrent.Await;
@@ -34,7 +42,8 @@ import scala.concurrent.Future;
 
 /**
  * TestActorFactory provides methods to create both normal and test actors and to kill them when the factory is closed
- * The ideal usage for TestActorFactory is with try with resources, <br/>
+ * The ideal usage for TestActorFactory is with try with resources.
+ * <p/>
  * For example <br/>
  * <pre>
  *     try (TestActorFactory factory = new TestActorFactory(getSystem())){
@@ -45,95 +54,130 @@ import scala.concurrent.Future;
  * </pre>
  */
 public class TestActorFactory implements AutoCloseable {
+    private static final Logger LOG = LoggerFactory.getLogger(TestActorFactory.class);
+
     private final ActorSystem system;
     List<ActorRef> createdActors = new LinkedList<>();
-    Logger LOG = LoggerFactory.getLogger(getClass());
     private static int actorCount = 1;
 
-    public TestActorFactory(ActorSystem system){
+    public TestActorFactory(ActorSystem system) {
         this.system = system;
     }
 
     /**
-     * Create a normal actor with an auto-generated name
+     * Create a normal actor with an auto-generated name.
      *
-     * @param props
-     * @return
+     * @param props the actor Props
+     * @return the ActorRef
      */
-    public ActorRef createActor(Props props){
+    public ActorRef createActor(Props props) {
         ActorRef actorRef = system.actorOf(props);
-        return addActor(actorRef);
+        return addActor(actorRef, true);
+    }
+
+    /**
+     * Create a normal actor with the passed in name.
+     *
+     * @param props the actor Props
+     * @param actorId name of actor
+     * @return the ActorRef
+     */
+    public ActorRef createActor(Props props, String actorId) {
+        ActorRef actorRef = system.actorOf(props, actorId);
+        return addActor(actorRef, true);
     }
 
     /**
-     * Create a normal actor with the passed in name
-     * @param props
+     * Create a normal actor with the passed in name w/o verifying that the actor is ready.
+     *
+     * @param props the actor Props
      * @param actorId name of actor
-     * @return
+     * @return the ActorRef
      */
-    public ActorRef createActor(Props props, String actorId){
+    public ActorRef createActorNoVerify(Props props, String actorId) {
         ActorRef actorRef = system.actorOf(props, actorId);
-        return addActor(actorRef);
+        return addActor(actorRef, false);
+    }
+
+    /**
+     * Create a test actor with the passed in name.
+     *
+     * @param props the actor Props
+     * @param actorId name of actor
+     * @param <T> the actor type
+     * @return the ActorRef
+     */
+    @SuppressWarnings("unchecked")
+    public <T extends Actor> TestActorRef<T> createTestActor(Props props, String actorId) {
+        InvalidActorNameException lastError = null;
+        for (int i = 0; i < 10; i++) {
+            try {
+                TestActorRef<T> actorRef = TestActorRef.create(system, props, actorId);
+                return (TestActorRef<T>) addActor(actorRef, true);
+            } catch (InvalidActorNameException e) {
+                lastError = e;
+                Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
+            }
+        }
+
+        throw lastError;
     }
 
     /**
-     * Create a test actor with the passed in name
-     * @param props
-     * @param actorId
-     * @param <T>
-     * @return
+     * Create a test actor with an auto-generated name.
+     *
+     * @param props the actor Props
+     * @param <T> the actor type
+     * @return the TestActorRef
      */
     @SuppressWarnings("unchecked")
-    public <T extends Actor> TestActorRef<T> createTestActor(Props props, String actorId){
-        TestActorRef<T> actorRef = TestActorRef.create(system, props, actorId);
-        return (TestActorRef<T>) addActor(actorRef);
+    public <T extends Actor> TestActorRef<T> createTestActor(Props props{
+        TestActorRef<T> actorRef = TestActorRef.create(system, props);
+        return (TestActorRef<T>) addActor(actorRef, true);
     }
 
-    private <T extends ActorRef> ActorRef addActor(T actorRef) {
+    private <T extends ActorRef> ActorRef addActor(T actorRef, boolean verify) {
         createdActors.add(actorRef);
-        verifyActorReady(actorRef);
+        if (verify) {
+            verifyActorReady(actorRef);
+        }
+
         return actorRef;
     }
 
+    @SuppressWarnings("checkstyle:IllegalCatch")
     private void verifyActorReady(ActorRef actorRef) {
         // Sometimes we see messages go to dead letters soon after creation - it seems the actor isn't quite
         // in a state yet to receive messages or isn't actually created yet. This seems to happen with
-        // actorSelection so, to alleviate it, we use an actorSelection and call resolveOne with retries to
-        // ensure it's ready.
+        // actorSelection so, to alleviate it, we use an actorSelection and send an Identify message with
+        // retries to ensure it's ready.
 
-        int tries = 1;
-        while(true) {
+        Timeout timeout = new Timeout(100, TimeUnit.MILLISECONDS);
+        Throwable lastError = null;
+        Stopwatch sw = Stopwatch.createStarted();
+        while (sw.elapsed(TimeUnit.SECONDS) <= 10) {
             try {
-                Timeout timeout = new Timeout(100, TimeUnit.MILLISECONDS);
-                Future<ActorRef> future = system.actorSelection(actorRef.path()).resolveOne(timeout);
-                Await.ready(future, timeout.duration());
-                break;
-            } catch (Exception e) {
-                if(tries++ > 20) {
-                    throw new RuntimeException(e);
-                }
+                ActorSelection actorSelection = system.actorSelection(actorRef.path().toString());
+                Future<Object> future = Patterns.ask(actorSelection, new Identify(""), timeout);
+                ActorIdentity reply = (ActorIdentity)Await.result(future, timeout.duration());
+                Assert.assertNotNull("Identify returned null", reply.getRef());
+                return;
+            } catch (Exception | AssertionError e) {
+                Uninterruptibles.sleepUninterruptibly(100, TimeUnit.MILLISECONDS);
+                lastError = e;
             }
         }
-    }
 
-    /**
-     * Create a test actor with an auto-generated name
-     * @param props
-     * @param <T>
-     * @return
-     */
-    @SuppressWarnings("unchecked")
-    public <T extends Actor> TestActorRef<T> createTestActor(Props props){
-        TestActorRef<T> actorRef = TestActorRef.create(system, props);
-        return (TestActorRef<T>) addActor(actorRef);
+        throw new RuntimeException(lastError);
     }
 
     /**
-     * Generate a friendly but unique actor id/name
-     * @param prefix
-     * @return
+     * Generate a friendly but unique actor id/name.
+     *
+     * @param prefix the name prefix
+     * @return the actor name
      */
-    public String generateActorId(String prefix){
+    public String generateActorId(String prefix) {
         return prefix + actorCount++;
     }
 
@@ -147,16 +191,20 @@ public class TestActorFactory implements AutoCloseable {
         actor.tell(PoisonPill.getInstance(), ActorRef.noSender());
         kit.expectTerminated(JavaTestKit.duration("5 seconds"), actor);
 
-        if(remove) {
+        if (remove) {
             createdActors.remove(actor);
         }
     }
 
+    public String createTestActorPath(String actorId) {
+        return "akka://test/user/" + actorId;
+    }
+
     @Override
     public void close() {
         JavaTestKit kit = new JavaTestKit(system);
-        for(ActorRef actor : createdActors) {
+        for (ActorRef actor : createdActors) {
             killActor(actor, kit, false);
         }
     }
-}
\ No newline at end of file
+}