Merge branch 'master' into wip-2201-cache-node-lookup-patriknw
Conflicts: akka-cluster/src/multi-jvm/scala/akka/cluster/ConvergenceSpec.scala akka-cluster/src/multi-jvm/scala/akka/cluster/LeaderElectionSpec.scala akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerExitingSpec.scala akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerJoinSpec.scala akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerLeavingSpec.scala akka-cluster/src/multi-jvm/scala/akka/cluster/MembershipChangeListenerUpSpec.scala akka-cluster/src/multi-jvm/scala/akka/cluster/NodeLeavingAndExitingAndBeingRemovedSpec.scala akka-cluster/src/multi-jvm/scala/akka/cluster/TransitionSpec.scala
This commit is contained in:
commit
ec9abb12df
64 changed files with 1470 additions and 806 deletions
|
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.actor;
|
||||
|
||||
public class NonPublicClass {
|
||||
public static Props createProps() {
|
||||
return new Props(MyNonPublicActorClass.class);
|
||||
}
|
||||
}
|
||||
|
||||
class MyNonPublicActorClass extends UntypedActor {
|
||||
@Override public void onReceive(Object msg) {
|
||||
getSender().tell(msg);
|
||||
}
|
||||
}
|
||||
|
|
@ -358,6 +358,13 @@ class ActorRefSpec extends AkkaSpec with DefaultTimeout {
|
|||
system.stop(serverRef)
|
||||
}
|
||||
|
||||
"support actorOfs where the class of the actor isn't public" in {
|
||||
val a = system.actorOf(NonPublicClass.createProps())
|
||||
a.tell("pigdog", testActor)
|
||||
expectMsg("pigdog")
|
||||
system stop a
|
||||
}
|
||||
|
||||
"stop when sent a poison pill" in {
|
||||
val timeout = Timeout(20000)
|
||||
val ref = system.actorOf(Props(new Actor {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ package akka
|
|||
* <ul>
|
||||
* <li>a uuid for tracking purposes</li>
|
||||
* <li>toString that includes exception name, message and uuid</li>
|
||||
* <li>toLongString which also includes the stack trace</li>
|
||||
* </ul>
|
||||
*/
|
||||
//TODO add @SerialVersionUID(1L) when SI-4804 is fixed
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ package akka.actor
|
|||
import akka.AkkaException
|
||||
import scala.reflect.BeanProperty
|
||||
import scala.util.control.NoStackTrace
|
||||
import scala.collection.immutable.Stack
|
||||
import java.util.regex.Pattern
|
||||
|
||||
/**
|
||||
|
|
@ -279,18 +278,14 @@ trait Actor {
|
|||
*/
|
||||
protected[akka] implicit val context: ActorContext = {
|
||||
val contextStack = ActorCell.contextStack.get
|
||||
|
||||
def noContextError =
|
||||
if ((contextStack.isEmpty) || (contextStack.head eq null))
|
||||
throw new ActorInitializationException(
|
||||
"\n\tYou cannot create an instance of [" + getClass.getName + "] explicitly using the constructor (new)." +
|
||||
"\n\tYou have to use one of the factory methods to create a new actor. Either use:" +
|
||||
"\n\t\t'val actor = context.actorOf(Props[MyActor])' (to create a supervised child actor from within an actor), or" +
|
||||
"\n\t\t'val actor = system.actorOf(Props(new MyActor(..)))' (to create a top level actor from the ActorSystem)")
|
||||
|
||||
if (contextStack.isEmpty) noContextError
|
||||
val c = contextStack.head
|
||||
if (c eq null) noContextError
|
||||
ActorCell.contextStack.set(contextStack.push(null))
|
||||
ActorCell.contextStack.set(null :: contextStack)
|
||||
c
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import akka.japi.Procedure
|
|||
import java.io.{ NotSerializableException, ObjectOutputStream }
|
||||
import akka.serialization.SerializationExtension
|
||||
import akka.event.Logging.LogEventException
|
||||
import collection.immutable.{ TreeSet, Stack, TreeMap }
|
||||
import collection.immutable.{ TreeSet, TreeMap }
|
||||
import akka.util.{ Unsafe, Duration, Helpers, NonFatal }
|
||||
|
||||
//TODO: everything here for current compatibility - could be limited more
|
||||
|
|
@ -173,8 +173,8 @@ trait UntypedActorContext extends ActorContext {
|
|||
* for! (waves hand)
|
||||
*/
|
||||
private[akka] object ActorCell {
|
||||
val contextStack = new ThreadLocal[Stack[ActorContext]] {
|
||||
override def initialValue = Stack[ActorContext]()
|
||||
val contextStack = new ThreadLocal[List[ActorContext]] {
|
||||
override def initialValue: List[ActorContext] = Nil
|
||||
}
|
||||
|
||||
final val emptyCancellable: Cancellable = new Cancellable {
|
||||
|
|
@ -184,7 +184,7 @@ private[akka] object ActorCell {
|
|||
|
||||
final val emptyReceiveTimeoutData: (Long, Cancellable) = (-1, emptyCancellable)
|
||||
|
||||
final val behaviorStackPlaceHolder: Stack[Actor.Receive] = Stack.empty.push(Actor.emptyBehavior)
|
||||
final val emptyBehaviorStack: List[Actor.Receive] = Nil
|
||||
|
||||
final val emptyActorRefSet: Set[ActorRef] = TreeSet.empty
|
||||
|
||||
|
|
@ -408,7 +408,7 @@ private[akka] class ActorCell(
|
|||
|
||||
var currentMessage: Envelope = _
|
||||
var actor: Actor = _
|
||||
private var behaviorStack: Stack[Actor.Receive] = Stack.empty
|
||||
private var behaviorStack: List[Actor.Receive] = emptyBehaviorStack
|
||||
@volatile var _mailboxDoNotCallMeDirectly: Mailbox = _ //This must be volatile since it isn't protected by the mailbox status
|
||||
var nextNameSequence: Long = 0
|
||||
var watching: Set[ActorRef] = emptyActorRefSet
|
||||
|
|
@ -511,25 +511,21 @@ private[akka] class ActorCell(
|
|||
|
||||
//This method is in charge of setting up the contextStack and create a new instance of the Actor
|
||||
protected def newActor(): Actor = {
|
||||
contextStack.set(contextStack.get.push(this))
|
||||
contextStack.set(this :: contextStack.get)
|
||||
try {
|
||||
import ActorCell.behaviorStackPlaceHolder
|
||||
|
||||
behaviorStack = behaviorStackPlaceHolder
|
||||
behaviorStack = emptyBehaviorStack
|
||||
val instance = props.creator.apply()
|
||||
|
||||
if (instance eq null)
|
||||
throw new ActorInitializationException(self, "Actor instance passed to actorOf can't be 'null'")
|
||||
|
||||
behaviorStack = behaviorStack match {
|
||||
case `behaviorStackPlaceHolder` ⇒ Stack.empty.push(instance.receive)
|
||||
case newBehaviors ⇒ Stack.empty.push(instance.receive).pushAll(newBehaviors.reverse.drop(1))
|
||||
}
|
||||
// If no becomes were issued, the actors behavior is its receive method
|
||||
behaviorStack = if (behaviorStack.isEmpty) instance.receive :: behaviorStack else behaviorStack
|
||||
instance
|
||||
} finally {
|
||||
val stackAfter = contextStack.get
|
||||
if (stackAfter.nonEmpty)
|
||||
contextStack.set(if (stackAfter.head eq null) stackAfter.pop.pop else stackAfter.pop) // pop null marker plus our context
|
||||
contextStack.set(if (stackAfter.head eq null) stackAfter.tail.tail else stackAfter.tail) // pop null marker plus our context
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -683,10 +679,8 @@ private[akka] class ActorCell(
|
|||
}
|
||||
}
|
||||
|
||||
def become(behavior: Actor.Receive, discardOld: Boolean = true): Unit = {
|
||||
if (discardOld) unbecome()
|
||||
behaviorStack = behaviorStack.push(behavior)
|
||||
}
|
||||
def become(behavior: Actor.Receive, discardOld: Boolean = true): Unit =
|
||||
behaviorStack = behavior :: (if (discardOld && behaviorStack.nonEmpty) behaviorStack.tail else behaviorStack)
|
||||
|
||||
/**
|
||||
* UntypedActorContext impl
|
||||
|
|
@ -701,8 +695,9 @@ private[akka] class ActorCell(
|
|||
|
||||
def unbecome(): Unit = {
|
||||
val original = behaviorStack
|
||||
val popped = original.pop
|
||||
behaviorStack = if (popped.isEmpty) original else popped
|
||||
behaviorStack =
|
||||
if (original.isEmpty || original.tail.isEmpty) actor.receive :: emptyBehaviorStack
|
||||
else original.tail
|
||||
}
|
||||
|
||||
def autoReceiveMessage(msg: Envelope): Unit = {
|
||||
|
|
@ -761,7 +756,7 @@ private[akka] class ActorCell(
|
|||
if (system.settings.DebugLifecycle)
|
||||
system.eventStream.publish(Debug(self.path.toString, clazz(a), "stopped"))
|
||||
} finally {
|
||||
behaviorStack = behaviorStackPlaceHolder
|
||||
behaviorStack = emptyBehaviorStack
|
||||
clearActorFields(a)
|
||||
actor = null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ import java.io.Closeable
|
|||
import akka.dispatch.Await.{ Awaitable, CanAwait }
|
||||
import akka.util._
|
||||
import akka.util.internal.{ HashedWheelTimer, ConcurrentIdentityHashMap }
|
||||
import collection.immutable.Stack
|
||||
import java.util.concurrent.{ ThreadFactory, CountDownLatch, TimeoutException, RejectedExecutionException }
|
||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
|
||||
|
|
@ -430,7 +429,7 @@ private[akka] class ActorSystemImpl(val name: String, applicationConfig: Config,
|
|||
if (!name.matches("""^[a-zA-Z0-9][a-zA-Z0-9-]*$"""))
|
||||
throw new IllegalArgumentException(
|
||||
"invalid ActorSystem name [" + name +
|
||||
"], must contain only word characters (i.e. [a-zA-Z_0-9] plus non-leading '-')")
|
||||
"], must contain only word characters (i.e. [a-zA-Z0-9] plus non-leading '-')")
|
||||
|
||||
import ActorSystem._
|
||||
|
||||
|
|
@ -685,8 +684,8 @@ private[akka] class ActorSystemImpl(val name: String, applicationConfig: Config,
|
|||
|
||||
final class TerminationCallbacks extends Runnable with Awaitable[Unit] {
|
||||
private val lock = new ReentrantGuard
|
||||
private var callbacks: Stack[Runnable] = _ //non-volatile since guarded by the lock
|
||||
lock withGuard { callbacks = Stack.empty[Runnable] }
|
||||
private var callbacks: List[Runnable] = _ //non-volatile since guarded by the lock
|
||||
lock withGuard { callbacks = Nil }
|
||||
|
||||
private val latch = new CountDownLatch(1)
|
||||
|
||||
|
|
@ -695,17 +694,17 @@ private[akka] class ActorSystemImpl(val name: String, applicationConfig: Config,
|
|||
case 0 ⇒ throw new RejectedExecutionException("Must be called prior to system shutdown.")
|
||||
case _ ⇒ lock withGuard {
|
||||
if (latch.getCount == 0) throw new RejectedExecutionException("Must be called prior to system shutdown.")
|
||||
else callbacks = callbacks.push(callback)
|
||||
else callbacks ::= callback
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final def run(): Unit = lock withGuard {
|
||||
@tailrec def runNext(c: Stack[Runnable]): Stack[Runnable] = c.headOption match {
|
||||
case None ⇒ Stack.empty[Runnable]
|
||||
case Some(callback) ⇒
|
||||
try callback.run() catch { case e ⇒ log.error(e, "Failed to run termination callback, due to [{}]", e.getMessage) }
|
||||
runNext(c.pop)
|
||||
@tailrec def runNext(c: List[Runnable]): List[Runnable] = c match {
|
||||
case Nil ⇒ Nil
|
||||
case callback :: rest ⇒
|
||||
try callback.run() catch { case NonFatal(e) ⇒ log.error(e, "Failed to run termination callback, due to [{}]", e.getMessage) }
|
||||
runNext(rest)
|
||||
}
|
||||
try { callbacks = runNext(callbacks) } finally latch.countDown()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ package akka.actor
|
|||
|
||||
import akka.dispatch._
|
||||
import akka.japi.Creator
|
||||
import collection.immutable.Stack
|
||||
import akka.routing._
|
||||
|
||||
/**
|
||||
|
|
@ -186,5 +185,10 @@ case class Props(
|
|||
* able to optimize serialization.
|
||||
*/
|
||||
private[akka] case class FromClassCreator(clazz: Class[_ <: Actor]) extends Function0[Actor] {
|
||||
def apply(): Actor = clazz.newInstance
|
||||
def apply(): Actor = try clazz.newInstance catch {
|
||||
case iae: IllegalAccessException ⇒
|
||||
val ctor = clazz.getDeclaredConstructor()
|
||||
ctor.setAccessible(true)
|
||||
ctor.newInstance()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,16 +9,22 @@ import TimeUnit._
|
|||
import java.lang.{ Double ⇒ JDouble }
|
||||
|
||||
//TODO add @SerialVersionUID(1L) when SI-4804 is fixed
|
||||
case class Deadline private (time: Duration) {
|
||||
case class Deadline private (time: Duration) extends Ordered[Deadline] {
|
||||
def +(other: Duration): Deadline = copy(time = time + other)
|
||||
def -(other: Duration): Deadline = copy(time = time - other)
|
||||
def -(other: Deadline): Duration = time - other.time
|
||||
def timeLeft: Duration = this - Deadline.now
|
||||
def hasTimeLeft(): Boolean = !isOverdue() //Code reuse FTW
|
||||
def isOverdue(): Boolean = (time.toNanos - System.nanoTime()) < 0
|
||||
def compare(that: Deadline) = this.time compare that.time
|
||||
}
|
||||
|
||||
object Deadline {
|
||||
def now: Deadline = Deadline(Duration(System.nanoTime, NANOSECONDS))
|
||||
|
||||
implicit object DeadlineIsOrdered extends Ordering[Deadline] {
|
||||
def compare(a: Deadline, b: Deadline) = a compare b
|
||||
}
|
||||
}
|
||||
|
||||
object Duration {
|
||||
|
|
|
|||
|
|
@ -165,8 +165,7 @@ class AccrualFailureDetector(
|
|||
else PhiFactor * timestampDiff / mean
|
||||
}
|
||||
|
||||
// FIXME change to debug log level, when failure detector is stable
|
||||
log.info("Phi value [{}] and threshold [{}] for connection [{}] ", phi, threshold, connection)
|
||||
log.debug("Phi value [{}] and threshold [{}] for connection [{}] ", phi, threshold, connection)
|
||||
phi
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,27 +6,27 @@ package akka.cluster
|
|||
|
||||
import akka.actor._
|
||||
import akka.actor.Status._
|
||||
import akka.ConfigurationException
|
||||
import akka.dispatch.Await
|
||||
import akka.dispatch.MonitorableThreadFactory
|
||||
import akka.event.Logging
|
||||
import akka.jsr166y.ThreadLocalRandom
|
||||
import akka.pattern.ask
|
||||
import akka.remote._
|
||||
import akka.routing._
|
||||
import akka.event.Logging
|
||||
import akka.dispatch.Await
|
||||
import akka.pattern.ask
|
||||
import akka.util._
|
||||
import akka.util.duration._
|
||||
import akka.ConfigurationException
|
||||
import java.util.concurrent.atomic.{ AtomicReference, AtomicBoolean }
|
||||
import java.util.concurrent.TimeUnit._
|
||||
import java.util.concurrent.TimeoutException
|
||||
import akka.jsr166y.ThreadLocalRandom
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.io.Closeable
|
||||
import javax.management._
|
||||
import scala.collection.immutable.{ Map, SortedSet }
|
||||
import scala.annotation.tailrec
|
||||
import com.google.protobuf.ByteString
|
||||
import akka.util.internal.HashedWheelTimer
|
||||
import akka.dispatch.MonitorableThreadFactory
|
||||
import com.google.protobuf.ByteString
|
||||
import java.io.Closeable
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.util.concurrent.atomic.{ AtomicReference, AtomicBoolean }
|
||||
import java.util.concurrent.TimeoutException
|
||||
import java.util.concurrent.TimeUnit._
|
||||
import javax.management._
|
||||
import MemberStatus._
|
||||
import scala.annotation.tailrec
|
||||
import scala.collection.immutable.{ Map, SortedSet }
|
||||
|
||||
/**
|
||||
* Interface for membership change listener.
|
||||
|
|
@ -52,7 +52,7 @@ sealed trait ClusterMessage extends Serializable
|
|||
/**
|
||||
* Cluster commands sent by the USER.
|
||||
*/
|
||||
object ClusterAction {
|
||||
object ClusterUserAction {
|
||||
|
||||
/**
|
||||
* Command to join the cluster. Sent when a node (reprsesented by 'address')
|
||||
|
|
@ -69,22 +69,33 @@ object ClusterAction {
|
|||
* Command to mark node as temporary down.
|
||||
*/
|
||||
case class Down(address: Address) extends ClusterMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Command to remove a node from the cluster immediately.
|
||||
* Cluster commands sent by the LEADER.
|
||||
*/
|
||||
case class Remove(address: Address) extends ClusterMessage
|
||||
object ClusterLeaderAction {
|
||||
|
||||
/**
|
||||
* INTERNAL API.
|
||||
*
|
||||
* Command to mark a node to be removed from the cluster immediately.
|
||||
* Can only be sent by the leader.
|
||||
*/
|
||||
private[akka] case class Exit(address: Address) extends ClusterMessage
|
||||
private[cluster] case class Exit(address: Address) extends ClusterMessage
|
||||
|
||||
/**
|
||||
* INTERNAL API.
|
||||
*
|
||||
* Command to remove a node from the cluster immediately.
|
||||
*/
|
||||
private[cluster] case class Remove(address: Address) extends ClusterMessage
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the address and the current status of a cluster member node.
|
||||
*
|
||||
* Note: `hashCode` and `equals` are solely based on the underlying `Address`, not its `MemberStatus`.
|
||||
*/
|
||||
class Member(val address: Address, val status: MemberStatus) extends ClusterMessage {
|
||||
override def hashCode = address.##
|
||||
|
|
@ -94,12 +105,12 @@ class Member(val address: Address, val status: MemberStatus) extends ClusterMess
|
|||
}
|
||||
|
||||
/**
|
||||
* Factory and Utility module for Member instances.
|
||||
* Module with factory and ordering methods for Member instances.
|
||||
*/
|
||||
object Member {
|
||||
|
||||
/**
|
||||
* Sort Address by host and port
|
||||
* `Address` ordering type class, sorts addresses by host and port.
|
||||
*/
|
||||
implicit val addressOrdering: Ordering[Address] = Ordering.fromLessThan[Address] { (a, b) ⇒
|
||||
if (a.host != b.host) a.host.getOrElse("").compareTo(b.host.getOrElse("")) < 0
|
||||
|
|
@ -107,8 +118,14 @@ object Member {
|
|||
else false
|
||||
}
|
||||
|
||||
implicit val ordering: Ordering[Member] = new Ordering[Member] {
|
||||
def compare(x: Member, y: Member) = addressOrdering.compare(x.address, y.address)
|
||||
/**
|
||||
* `Member` ordering type class, sorts members by host and port with the exception that
|
||||
* it puts all members that are in MemberStatus.EXITING last.
|
||||
*/
|
||||
implicit val ordering: Ordering[Member] = Ordering.fromLessThan[Member] { (a, b) ⇒
|
||||
if (a.status == Exiting && b.status != Exiting) false
|
||||
else if (a.status != Exiting && b.status == Exiting) true
|
||||
else addressOrdering.compare(a.address, b.address) < 0
|
||||
}
|
||||
|
||||
def apply(address: Address, status: MemberStatus): Member = new Member(address, status)
|
||||
|
|
@ -157,10 +174,11 @@ case class GossipEnvelope(from: Address, gossip: Gossip) extends ClusterMessage
|
|||
* Can be one of: Joining, Up, Leaving, Exiting and Down.
|
||||
*/
|
||||
sealed trait MemberStatus extends ClusterMessage {
|
||||
|
||||
/**
|
||||
* Using the same notion for 'unavailable' as 'non-convergence': DOWN and REMOVED.
|
||||
* Using the same notion for 'unavailable' as 'non-convergence': DOWN
|
||||
*/
|
||||
def isUnavailable: Boolean = this == Down || this == Removed
|
||||
def isUnavailable: Boolean = this == Down
|
||||
}
|
||||
|
||||
object MemberStatus {
|
||||
|
|
@ -193,28 +211,32 @@ object Gossip {
|
|||
* Represents the state of the cluster; cluster ring membership, ring convergence, meta data -
|
||||
* all versioned by a vector clock.
|
||||
*
|
||||
* When a node is joining the Member, with status Joining, is added to `members`.
|
||||
* If the joining node was downed it is moved from `overview.unreachable` (status Down)
|
||||
* to `members` (status Joining). It cannot rejoin if not first downed.
|
||||
* When a node is joining the `Member`, with status `Joining`, is added to `members`.
|
||||
* If the joining node was downed it is moved from `overview.unreachable` (status `Down`)
|
||||
* to `members` (status `Joining`). It cannot rejoin if not first downed.
|
||||
*
|
||||
* When convergence is reached the leader change status of `members` from Joining
|
||||
* to Up.
|
||||
* When convergence is reached the leader change status of `members` from `Joining`
|
||||
* to `Up`.
|
||||
*
|
||||
* When failure detector consider a node as unavailble it will be moved from
|
||||
* `members` to `overview.unreachable`.
|
||||
*
|
||||
* When a node is downed, either manually or automatically, its status is changed to Down.
|
||||
* It is also removed from `overview.seen` table.
|
||||
* The node will reside as Down in the `overview.unreachable` set until joining
|
||||
* again and it will then go through the normal joining procedure.
|
||||
* When a node is downed, either manually or automatically, its status is changed to `Down`.
|
||||
* It is also removed from `overview.seen` table. The node will reside as `Down` in the
|
||||
* `overview.unreachable` set until joining again and it will then go through the normal
|
||||
* joining procedure.
|
||||
*
|
||||
* When a Gossip is received the version (vector clock) is used to determine if the
|
||||
* received Gossip is newer or older than the current local Gossip. The received Gossip
|
||||
* and local Gossip is merged in case of conflicting version, i.e. vector clocks without
|
||||
* When a `Gossip` is received the version (vector clock) is used to determine if the
|
||||
* received `Gossip` is newer or older than the current local `Gossip`. The received `Gossip`
|
||||
* and local `Gossip` is merged in case of conflicting version, i.e. vector clocks without
|
||||
* same history. When merged the seen table is cleared.
|
||||
*
|
||||
* TODO document leaving, exiting and removed when that is implemented
|
||||
*
|
||||
* When a node is told by the user to leave the cluster the leader will move it to `Leaving`
|
||||
* and then rebalance and repartition the cluster and start hand-off by migrating the actors
|
||||
* from the leaving node to the new partitions. Once this process is complete the leader will
|
||||
* move the node to the `Exiting` state and once a convergence is complete move the node to
|
||||
* `Removed` by removing it from the `members` set and sending a `Removed` command to the
|
||||
* removed node telling it to shut itself down.
|
||||
*/
|
||||
case class Gossip(
|
||||
overview: GossipOverview = GossipOverview(),
|
||||
|
|
@ -226,6 +248,7 @@ case class Gossip(
|
|||
|
||||
// FIXME can be disabled as optimization
|
||||
assertInvariants
|
||||
|
||||
private def assertInvariants: Unit = {
|
||||
val unreachableAndLive = members.intersect(overview.unreachable)
|
||||
if (unreachableAndLive.nonEmpty)
|
||||
|
|
@ -251,14 +274,17 @@ case class Gossip(
|
|||
*/
|
||||
def :+(node: VectorClock.Node): Gossip = copy(version = version :+ node)
|
||||
|
||||
/**
|
||||
* Adds a member to the member node ring.
|
||||
*/
|
||||
def :+(member: Member): Gossip = {
|
||||
if (members contains member) this
|
||||
else this copy (members = members + member)
|
||||
}
|
||||
|
||||
/**
|
||||
* Marks the gossip as seen by this node (selfAddress) by updating the address entry in the 'gossip.overview.seen'
|
||||
* Map with the VectorClock for the new gossip.
|
||||
* Marks the gossip as seen by this node (address) by updating the address entry in the 'gossip.overview.seen'
|
||||
* Map with the VectorClock (version) for the new gossip.
|
||||
*/
|
||||
def seen(address: Address): Gossip = {
|
||||
if (overview.seen.contains(address) && overview.seen(address) == version) this
|
||||
|
|
@ -282,8 +308,7 @@ case class Gossip(
|
|||
|
||||
// 4. merge members by selecting the single Member with highest MemberStatus out of the Member groups,
|
||||
// and exclude unreachable
|
||||
val mergedMembers = Gossip.emptyMembers ++ Member.pickHighestPriority(this.members, that.members).
|
||||
filterNot(mergedUnreachable.contains)
|
||||
val mergedMembers = Gossip.emptyMembers ++ Member.pickHighestPriority(this.members, that.members).filterNot(mergedUnreachable.contains)
|
||||
|
||||
// 5. fresh seen table
|
||||
val mergedSeen = Map.empty[Address, VectorClock]
|
||||
|
|
@ -306,11 +331,14 @@ case class Gossip(
|
|||
case class Heartbeat(from: Address) extends ClusterMessage
|
||||
|
||||
/**
|
||||
* INTERNAL API.
|
||||
*
|
||||
* Manages routing of the different cluster commands.
|
||||
* Instantiated as a single instance for each Cluster - e.g. commands are serialized to Cluster message after message.
|
||||
*/
|
||||
private[akka] final class ClusterCommandDaemon(cluster: Cluster) extends Actor {
|
||||
import ClusterAction._
|
||||
private[cluster] final class ClusterCommandDaemon(cluster: Cluster) extends Actor {
|
||||
import ClusterUserAction._
|
||||
import ClusterLeaderAction._
|
||||
|
||||
val log = Logging(context.system, this)
|
||||
|
||||
|
|
@ -326,10 +354,12 @@ private[akka] final class ClusterCommandDaemon(cluster: Cluster) extends Actor {
|
|||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API.
|
||||
*
|
||||
* Pooled and routed with N number of configurable instances.
|
||||
* Concurrent access to Cluster.
|
||||
*/
|
||||
private[akka] final class ClusterGossipDaemon(cluster: Cluster) extends Actor {
|
||||
private[cluster] final class ClusterGossipDaemon(cluster: Cluster) extends Actor {
|
||||
val log = Logging(context.system, this)
|
||||
|
||||
def receive = {
|
||||
|
|
@ -341,9 +371,11 @@ private[akka] final class ClusterGossipDaemon(cluster: Cluster) extends Actor {
|
|||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API.
|
||||
*
|
||||
* Supervisor managing the different Cluster daemons.
|
||||
*/
|
||||
private[akka] final class ClusterDaemonSupervisor(cluster: Cluster) extends Actor {
|
||||
private[cluster] final class ClusterDaemonSupervisor(cluster: Cluster) extends Actor {
|
||||
val log = Logging(context.system, this)
|
||||
|
||||
private val commands = context.actorOf(Props(new ClusterCommandDaemon(cluster)), "commands")
|
||||
|
|
@ -396,13 +428,11 @@ trait ClusterNodeMBean {
|
|||
def isSingleton: Boolean
|
||||
def isConvergence: Boolean
|
||||
def isAvailable: Boolean
|
||||
def isRunning: Boolean
|
||||
|
||||
def join(address: String)
|
||||
def leave(address: String)
|
||||
def down(address: String)
|
||||
def remove(address: String)
|
||||
|
||||
def shutdown()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -455,7 +485,7 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
|
||||
private val serialization = remote.serialization
|
||||
|
||||
private val isRunning = new AtomicBoolean(true)
|
||||
private val _isRunning = new AtomicBoolean(true)
|
||||
private val log = Logging(system, "Node")
|
||||
|
||||
private val mBeanServer = ManagementFactory.getPlatformMBeanServer
|
||||
|
|
@ -566,15 +596,27 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the cluster node is up and running, false if it is shut down.
|
||||
*/
|
||||
def isRunning: Boolean = _isRunning.get
|
||||
|
||||
/**
|
||||
* Latest gossip.
|
||||
*/
|
||||
def latestGossip: Gossip = state.get.latestGossip
|
||||
|
||||
/**
|
||||
* Member status for this node.
|
||||
* Member status for this node (`MemberStatus`).
|
||||
*
|
||||
* NOTE: If the node has been removed from the cluster (and shut down) then it's status is set to the 'REMOVED' tombstone state
|
||||
* and is no longer present in the node ring or any other part of the gossiping state. However in order to maintain the
|
||||
* model and the semantics the user would expect, this method will in this situation return `MemberStatus.Removed`.
|
||||
*/
|
||||
def status: MemberStatus = self.status
|
||||
def status: MemberStatus = {
|
||||
if (isRunning) self.status
|
||||
else MemberStatus.Removed
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this node the leader?
|
||||
|
|
@ -606,33 +648,6 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
*/
|
||||
def isAvailable: Boolean = !isUnavailable(state.get)
|
||||
|
||||
/**
|
||||
* Shuts down all connections to other members, the cluster daemon and the periodic gossip and cleanup tasks.
|
||||
*/
|
||||
def shutdown(): Unit = {
|
||||
if (isRunning.compareAndSet(true, false)) {
|
||||
log.info("Cluster Node [{}] - Shutting down cluster Node and cluster daemons...", selfAddress)
|
||||
|
||||
// cancel the periodic tasks, note that otherwise they will be run when scheduler is shutdown
|
||||
gossipTask.cancel()
|
||||
heartbeatTask.cancel()
|
||||
failureDetectorReaperTask.cancel()
|
||||
leaderActionsTask.cancel()
|
||||
clusterScheduler.close()
|
||||
|
||||
// FIXME isTerminated check can be removed when ticket #2221 is fixed
|
||||
// now it prevents logging if system is shutdown (or in progress of shutdown)
|
||||
if (!clusterDaemons.isTerminated)
|
||||
system.stop(clusterDaemons)
|
||||
|
||||
try {
|
||||
mBeanServer.unregisterMBean(clusterMBeanName)
|
||||
} catch {
|
||||
case e: InstanceNotFoundException ⇒ // ignore - we are running multiple cluster nodes in the same JVM (probably for testing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener to subscribe to cluster membership changes.
|
||||
*/
|
||||
|
|
@ -661,7 +676,7 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
*/
|
||||
def join(address: Address): Unit = {
|
||||
val connection = clusterCommandConnectionFor(address)
|
||||
val command = ClusterAction.Join(selfAddress)
|
||||
val command = ClusterUserAction.Join(selfAddress)
|
||||
log.info("Cluster Node [{}] - Trying to send JOIN to [{}] through connection [{}]", selfAddress, address, connection)
|
||||
connection ! command
|
||||
}
|
||||
|
|
@ -670,21 +685,14 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
* Send command to issue state transition to LEAVING for the node specified by 'address'.
|
||||
*/
|
||||
def leave(address: Address): Unit = {
|
||||
clusterCommandDaemon ! ClusterAction.Leave(address)
|
||||
clusterCommandDaemon ! ClusterUserAction.Leave(address)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send command to issue state transition to from DOWN to EXITING for the node specified by 'address'.
|
||||
* Send command to DOWN the node specified by 'address'.
|
||||
*/
|
||||
def down(address: Address): Unit = {
|
||||
clusterCommandDaemon ! ClusterAction.Down(address)
|
||||
}
|
||||
|
||||
/**
|
||||
* Send command to issue state transition to REMOVED for the node specified by 'address'.
|
||||
*/
|
||||
def remove(address: Address): Unit = {
|
||||
clusterCommandDaemon ! ClusterAction.Remove(address)
|
||||
clusterCommandDaemon ! ClusterUserAction.Down(address)
|
||||
}
|
||||
|
||||
// ========================================================
|
||||
|
|
@ -692,22 +700,52 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
// ========================================================
|
||||
|
||||
/**
|
||||
* State transition to JOINING.
|
||||
* New node joining.
|
||||
* INTERNAL API.
|
||||
*
|
||||
* Shuts down all connections to other members, the cluster daemon and the periodic gossip and cleanup tasks.
|
||||
*
|
||||
* Should not called by the user. The user can issue a LEAVE command which will tell the node
|
||||
* to go through graceful handoff process `LEAVE -> EXITING -> REMOVED -> SHUTDOWN`.
|
||||
*/
|
||||
private[cluster] def shutdown(): Unit = {
|
||||
if (_isRunning.compareAndSet(true, false)) {
|
||||
log.info("Cluster Node [{}] - Shutting down cluster Node and cluster daemons...", selfAddress)
|
||||
|
||||
// cancel the periodic tasks, note that otherwise they will be run when scheduler is shutdown
|
||||
gossipTask.cancel()
|
||||
heartbeatTask.cancel()
|
||||
failureDetectorReaperTask.cancel()
|
||||
leaderActionsTask.cancel()
|
||||
clusterScheduler.close()
|
||||
|
||||
// FIXME isTerminated check can be removed when ticket #2221 is fixed
|
||||
// now it prevents logging if system is shutdown (or in progress of shutdown)
|
||||
if (!clusterDaemons.isTerminated)
|
||||
system.stop(clusterDaemons)
|
||||
|
||||
try {
|
||||
mBeanServer.unregisterMBean(clusterMBeanName)
|
||||
} catch {
|
||||
case e: InstanceNotFoundException ⇒ // ignore - we are running multiple cluster nodes in the same JVM (probably for testing)
|
||||
}
|
||||
log.info("Cluster Node [{}] - Cluster node successfully shut down", selfAddress)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API.
|
||||
*
|
||||
* State transition to JOINING - new node joining.
|
||||
*/
|
||||
@tailrec
|
||||
private[cluster] final def joining(node: Address): Unit = {
|
||||
log.info("Cluster Node [{}] - Node [{}] is JOINING", selfAddress, node)
|
||||
|
||||
val localState = state.get
|
||||
val localGossip = localState.latestGossip
|
||||
val localMembers = localGossip.members
|
||||
val localUnreachable = localGossip.overview.unreachable
|
||||
|
||||
val alreadyMember = localMembers.exists(_.address == node)
|
||||
val isUnreachable = localUnreachable.exists { m ⇒
|
||||
m.address == node && m.status != Down && m.status != Removed
|
||||
}
|
||||
val isUnreachable = localUnreachable.exists { m ⇒ m.address == node && m.status != Down }
|
||||
|
||||
if (!alreadyMember && !isUnreachable) {
|
||||
|
||||
|
|
@ -725,6 +763,7 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
|
||||
if (!state.compareAndSet(localState, newState)) joining(node) // recur if we failed update
|
||||
else {
|
||||
log.info("Cluster Node [{}] - Node [{}] is JOINING", selfAddress, node)
|
||||
// treat join as initial heartbeat, so that it becomes unavailable if nothing more happens
|
||||
if (node != selfAddress) failureDetector heartbeat node
|
||||
notifyMembershipChangeListeners(localState, newState)
|
||||
|
|
@ -733,17 +772,16 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API.
|
||||
*
|
||||
* State transition to LEAVING.
|
||||
*/
|
||||
@tailrec
|
||||
private[cluster] final def leaving(address: Address) {
|
||||
log.info("Cluster Node [{}] - Marking address [{}] as LEAVING", selfAddress, address)
|
||||
|
||||
val localState = state.get
|
||||
val localGossip = localState.latestGossip
|
||||
val localMembers = localGossip.members
|
||||
|
||||
val newMembers = localMembers + Member(address, Leaving) // mark node as LEAVING
|
||||
if (localGossip.members.exists(_.address == address)) { // only try to update if the node is available (in the member ring)
|
||||
val newMembers = localGossip.members map { member ⇒ if (member.address == address) Member(address, Leaving) else member } // mark node as LEAVING
|
||||
val newGossip = localGossip copy (members = newMembers)
|
||||
|
||||
val versionedGossip = newGossip :+ vclockNode
|
||||
|
|
@ -753,32 +791,41 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
|
||||
if (!state.compareAndSet(localState, newState)) leaving(address) // recur if we failed update
|
||||
else {
|
||||
log.info("Cluster Node [{}] - Marked address [{}] as LEAVING", selfAddress, address)
|
||||
notifyMembershipChangeListeners(localState, newState)
|
||||
}
|
||||
}
|
||||
|
||||
private def notifyMembershipChangeListeners(oldState: State, newState: State): Unit = {
|
||||
val oldMembersStatus = oldState.latestGossip.members.toSeq.map(m ⇒ (m.address, m.status))
|
||||
val newMembersStatus = newState.latestGossip.members.toSeq.map(m ⇒ (m.address, m.status))
|
||||
if (newMembersStatus != oldMembersStatus)
|
||||
newState.memberMembershipChangeListeners foreach { _ notify newState.latestGossip.members }
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API.
|
||||
*
|
||||
* State transition to EXITING.
|
||||
*/
|
||||
private[cluster] final def exiting(address: Address): Unit = {
|
||||
log.info("Cluster Node [{}] - Marking node [{}] as EXITING", selfAddress, address)
|
||||
log.info("Cluster Node [{}] - Marked node [{}] as EXITING", selfAddress, address)
|
||||
// FIXME implement when we implement hand-off
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API.
|
||||
*
|
||||
* State transition to REMOVED.
|
||||
*
|
||||
* This method is for now only called after the LEADER have sent a Removed message - telling the node
|
||||
* to shut down himself.
|
||||
*
|
||||
* In the future we might change this to allow the USER to send a Removed(address) message telling an
|
||||
* arbitrary node to be moved direcly from UP -> REMOVED.
|
||||
*/
|
||||
private[cluster] final def removing(address: Address): Unit = {
|
||||
log.info("Cluster Node [{}] - Marking node [{}] as REMOVED", selfAddress, address)
|
||||
log.info("Cluster Node [{}] - Node has been REMOVED by the leader - shutting down...", selfAddress)
|
||||
shutdown()
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API.
|
||||
*
|
||||
* The node to DOWN is removed from the 'members' set and put in the 'unreachable' set (if not already there)
|
||||
* and its status is set to DOWN. The node is also removed from the 'seen' table.
|
||||
*
|
||||
|
|
@ -836,6 +883,8 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API.
|
||||
*
|
||||
* Receive new gossip.
|
||||
*/
|
||||
@tailrec
|
||||
|
|
@ -849,9 +898,8 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
val mergedGossip = remoteGossip merge localGossip
|
||||
val versionedMergedGossip = mergedGossip :+ vclockNode
|
||||
|
||||
// FIXME change to debug log level, when failure detector is stable
|
||||
log.info(
|
||||
"""Can't establish a causal relationship between "remote" gossip [{}] and "local" gossip [{}] - merging them into [{}]""",
|
||||
log.debug(
|
||||
"""Can't establish a causal relationship between "remote" gossip and "local" gossip - Remote[{}] - Local[{}] - merging them into [{}]""",
|
||||
remoteGossip, localGossip, versionedMergedGossip)
|
||||
|
||||
versionedMergedGossip
|
||||
|
|
@ -876,7 +924,7 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
* INTERNAL API.
|
||||
*/
|
||||
private[cluster] def receiveHeartbeat(from: Address): Unit = failureDetector heartbeat from
|
||||
|
||||
|
|
@ -886,11 +934,11 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
private def autoJoin(): Unit = nodeToJoin foreach join
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
* INTERNAL API.
|
||||
*
|
||||
* Gossips latest gossip to an address.
|
||||
*/
|
||||
private[akka] def gossipTo(address: Address): Unit = {
|
||||
private[cluster] def gossipTo(address: Address): Unit = {
|
||||
val connection = clusterGossipConnectionFor(address)
|
||||
log.debug("Cluster Node [{}] - Gossiping to [{}]", selfAddress, connection)
|
||||
connection ! GossipEnvelope(selfAddress, latestGossip)
|
||||
|
|
@ -910,18 +958,18 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
* INTERNAL API.
|
||||
*/
|
||||
private[akka] def gossipToUnreachableProbablity(membersSize: Int, unreachableSize: Int): Double =
|
||||
private[cluster] def gossipToUnreachableProbablity(membersSize: Int, unreachableSize: Int): Double =
|
||||
(membersSize + unreachableSize) match {
|
||||
case 0 ⇒ 0.0
|
||||
case sum ⇒ unreachableSize.toDouble / sum
|
||||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
* INTERNAL API.
|
||||
*/
|
||||
private[akka] def gossipToDeputyProbablity(membersSize: Int, unreachableSize: Int, nrOfDeputyNodes: Int): Double = {
|
||||
private[cluster] def gossipToDeputyProbablity(membersSize: Int, unreachableSize: Int, nrOfDeputyNodes: Int): Double = {
|
||||
if (nrOfDeputyNodes > membersSize) 1.0
|
||||
else if (nrOfDeputyNodes == 0) 0.0
|
||||
else (membersSize + unreachableSize) match {
|
||||
|
|
@ -931,11 +979,11 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
* INTERNAL API.
|
||||
*
|
||||
* Initates a new round of gossip.
|
||||
*/
|
||||
private[akka] def gossip(): Unit = {
|
||||
private[cluster] def gossip(): Unit = {
|
||||
val localState = state.get
|
||||
|
||||
log.debug("Cluster Node [{}] - Initiating new round of gossip", selfAddress)
|
||||
|
|
@ -972,9 +1020,9 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
* INTERNAL API.
|
||||
*/
|
||||
private[akka] def heartbeat(): Unit = {
|
||||
private[cluster] def heartbeat(): Unit = {
|
||||
val localState = state.get
|
||||
|
||||
if (!isSingletonCluster(localState)) {
|
||||
|
|
@ -989,12 +1037,12 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
* INTERNAL API.
|
||||
*
|
||||
* Reaps the unreachable members (moves them to the 'unreachable' list in the cluster overview) according to the failure detector's verdict.
|
||||
*/
|
||||
@tailrec
|
||||
final private[akka] def reapUnreachableMembers(): Unit = {
|
||||
final private[cluster] def reapUnreachableMembers(): Unit = {
|
||||
val localState = state.get
|
||||
|
||||
if (!isSingletonCluster(localState) && isAvailable(localState)) {
|
||||
|
|
@ -1033,124 +1081,181 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
}
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
* INTERNAL API.
|
||||
*
|
||||
* Runs periodic leader actions, such as auto-downing unreachable nodes, assigning partitions etc.
|
||||
*/
|
||||
@tailrec
|
||||
final private[akka] def leaderActions(): Unit = {
|
||||
final private[cluster] def leaderActions(): Unit = {
|
||||
val localState = state.get
|
||||
val localGossip = localState.latestGossip
|
||||
val localMembers = localGossip.members
|
||||
|
||||
val isLeader = localMembers.nonEmpty && (selfAddress == localMembers.head.address)
|
||||
|
||||
// FIXME implement partion handoff and a check if it is completed - now just returns TRUE - e.g. has completed successfully
|
||||
def hasPartionHandoffCompletedSuccessfully(gossip: Gossip): Boolean = {
|
||||
true
|
||||
}
|
||||
|
||||
if (isLeader && isAvailable(localState)) {
|
||||
// only run the leader actions if we are the LEADER and available
|
||||
|
||||
val localOverview = localGossip.overview
|
||||
val localSeen = localOverview.seen
|
||||
val localUnreachableMembers = localOverview.unreachable
|
||||
val hasPartionHandoffCompletedSuccessfully: Boolean = {
|
||||
// FIXME implement partion handoff and a check if it is completed - now just returns TRUE - e.g. has completed successfully
|
||||
true
|
||||
}
|
||||
|
||||
// Leader actions are as follows:
|
||||
// 1. Move JOINING => UP -- When a node joins the cluster
|
||||
// 2. Move EXITING => REMOVED -- When all nodes have seen that the node is EXITING (convergence)
|
||||
// 1. Move EXITING => REMOVED -- When all nodes have seen that the node is EXITING (convergence) - remove the nodes from the node ring and seen table
|
||||
// 2. Move JOINING => UP -- When a node joins the cluster
|
||||
// 3. Move LEAVING => EXITING -- When all partition handoff has completed
|
||||
// 4. Move UNREACHABLE => DOWN -- When the node is in the UNREACHABLE set it can be auto-down by leader
|
||||
// 5. Updating the vclock version for the changes
|
||||
// 6. Updating the 'seen' table
|
||||
// 5. Store away all stuff needed for the side-effecting processing in 10.
|
||||
// 6. Updating the vclock version for the changes
|
||||
// 7. Updating the 'seen' table
|
||||
// 8. Try to update the state with the new gossip
|
||||
// 9. If failure - retry
|
||||
// 10. If success - run all the side-effecting processing
|
||||
|
||||
var hasChangedState = false
|
||||
val newGossip =
|
||||
val (
|
||||
newGossip: Gossip,
|
||||
hasChangedState: Boolean,
|
||||
upMembers,
|
||||
exitingMembers,
|
||||
removedMembers,
|
||||
unreachableButNotDownedMembers) =
|
||||
|
||||
if (convergence(localGossip).isDefined) {
|
||||
// we have convergence - so we can't have unreachable nodes
|
||||
|
||||
// transform the node member ring - filterNot/map/map
|
||||
val newMembers =
|
||||
|
||||
localMembers map { member ⇒
|
||||
localMembers filterNot { member ⇒
|
||||
// ----------------------
|
||||
// 1. Move JOINING => UP (once all nodes have seen that this node is JOINING e.g. we have a convergence)
|
||||
// 1. Move EXITING => REMOVED - e.g. remove the nodes from the 'members' set/node ring and seen table
|
||||
// ----------------------
|
||||
if (member.status == Joining) {
|
||||
log.info("Cluster Node [{}] - Leader is moving node [{}] from JOINING to UP", selfAddress, member.address)
|
||||
hasChangedState = true
|
||||
member copy (status = Up)
|
||||
} else member
|
||||
member.status == MemberStatus.Exiting
|
||||
|
||||
} map { member ⇒
|
||||
// ----------------------
|
||||
// 2. Move EXITING => REMOVED (once all nodes have seen that this node is EXITING e.g. we have a convergence)
|
||||
// 2. Move JOINING => UP (once all nodes have seen that this node is JOINING e.g. we have a convergence)
|
||||
// ----------------------
|
||||
if (member.status == Exiting) {
|
||||
log.info("Cluster Node [{}] - Leader is moving node [{}] from EXITING to REMOVED", selfAddress, member.address)
|
||||
hasChangedState = true
|
||||
member copy (status = Removed)
|
||||
} else member
|
||||
if (member.status == Joining) member copy (status = Up)
|
||||
else member
|
||||
|
||||
} map { member ⇒
|
||||
// ----------------------
|
||||
// 3. Move LEAVING => EXITING (once we have a convergence on LEAVING *and* if we have a successful partition handoff)
|
||||
// ----------------------
|
||||
if (member.status == Leaving && hasPartionHandoffCompletedSuccessfully(localGossip)) {
|
||||
log.info("Cluster Node [{}] - Leader is moving node [{}] from LEAVING to EXITING", selfAddress, member.address)
|
||||
hasChangedState = true
|
||||
member copy (status = Exiting)
|
||||
} else member
|
||||
|
||||
if (member.status == Leaving && hasPartionHandoffCompletedSuccessfully) member copy (status = Exiting)
|
||||
else member
|
||||
}
|
||||
localGossip copy (members = newMembers) // update gossip
|
||||
|
||||
// ----------------------
|
||||
// 5. Store away all stuff needed for the side-effecting processing in 10.
|
||||
// ----------------------
|
||||
|
||||
// Check for the need to do side-effecting on successful state change
|
||||
// Repeat the checking for transitions between JOINING -> UP, LEAVING -> EXITING, EXITING -> REMOVED
|
||||
// to check for state-changes and to store away removed and exiting members for later notification
|
||||
// 1. check for state-changes to update
|
||||
// 2. store away removed and exiting members so we can separate the pure state changes (that can be retried on collision) and the side-effecting message sending
|
||||
val (removedMembers, newMembers1) = localMembers partition (_.status == Exiting)
|
||||
|
||||
val (upMembers, newMembers2) = newMembers1 partition (_.status == Joining)
|
||||
|
||||
val (exitingMembers, newMembers3) = newMembers2 partition (_.status == Leaving && hasPartionHandoffCompletedSuccessfully)
|
||||
|
||||
val hasChangedState = removedMembers.nonEmpty || upMembers.nonEmpty || exitingMembers.nonEmpty
|
||||
|
||||
// removing REMOVED nodes from the 'seen' table
|
||||
val newSeen = localSeen -- removedMembers.map(_.address)
|
||||
|
||||
// removing REMOVED nodes from the 'unreachable' set
|
||||
val newUnreachableMembers = localUnreachableMembers -- removedMembers
|
||||
|
||||
val newOverview = localOverview copy (seen = newSeen, unreachable = newUnreachableMembers) // update gossip overview
|
||||
val newGossip = localGossip copy (members = newMembers, overview = newOverview) // update gossip
|
||||
|
||||
(newGossip, hasChangedState, upMembers, exitingMembers, removedMembers, Set.empty[Member])
|
||||
|
||||
} else if (AutoDown) {
|
||||
// we don't have convergence - so we might have unreachable nodes
|
||||
// if 'auto-down' is turned on, then try to auto-down any unreachable nodes
|
||||
|
||||
// if 'auto-down' is turned on, then try to auto-down any unreachable nodes
|
||||
val newUnreachableMembers = localUnreachableMembers.map { member ⇒
|
||||
// ----------------------
|
||||
// 4. Move UNREACHABLE => DOWN (auto-downing by leader)
|
||||
// 5. Move UNREACHABLE => DOWN (auto-downing by leader)
|
||||
// ----------------------
|
||||
val newUnreachableMembers =
|
||||
localUnreachableMembers.map { member ⇒
|
||||
// no need to DOWN members already DOWN
|
||||
if (member.status == Down) member
|
||||
else {
|
||||
log.info("Cluster Node [{}] - Leader is marking unreachable node [{}] as DOWN", selfAddress, member.address)
|
||||
hasChangedState = true
|
||||
member copy (status = Down)
|
||||
}
|
||||
if (member.status == Down) member // no need to DOWN members already DOWN
|
||||
else member copy (status = Down)
|
||||
}
|
||||
|
||||
// Check for the need to do side-effecting on successful state change
|
||||
val (unreachableButNotDownedMembers, _) = localUnreachableMembers partition (_.status != Down)
|
||||
|
||||
// removing nodes marked as DOWN from the 'seen' table
|
||||
val newSeen = localSeen -- newUnreachableMembers.collect {
|
||||
case m if m.status == Down ⇒ m.address
|
||||
}
|
||||
val newSeen = localSeen -- newUnreachableMembers.collect { case m if m.status == Down ⇒ m.address }
|
||||
|
||||
val newOverview = localOverview copy (seen = newSeen, unreachable = newUnreachableMembers) // update gossip overview
|
||||
localGossip copy (overview = newOverview) // update gossip
|
||||
val newGossip = localGossip copy (overview = newOverview) // update gossip
|
||||
|
||||
} else localGossip
|
||||
(newGossip, unreachableButNotDownedMembers.nonEmpty, Set.empty[Member], Set.empty[Member], Set.empty[Member], unreachableButNotDownedMembers)
|
||||
|
||||
} else (localGossip, false, Set.empty[Member], Set.empty[Member], Set.empty[Member], Set.empty[Member])
|
||||
|
||||
if (hasChangedState) { // we have a change of state - version it and try to update
|
||||
|
||||
// ----------------------
|
||||
// 5. Updating the vclock version for the changes
|
||||
// 6. Updating the vclock version for the changes
|
||||
// ----------------------
|
||||
val versionedGossip = newGossip :+ vclockNode
|
||||
|
||||
// ----------------------
|
||||
// 6. Updating the 'seen' table
|
||||
// 7. Updating the 'seen' table
|
||||
// Unless the leader (this node) is part of the removed members, i.e. the leader have moved himself from EXITING -> REMOVED
|
||||
// ----------------------
|
||||
val seenVersionedGossip = versionedGossip seen selfAddress
|
||||
val seenVersionedGossip =
|
||||
if (removedMembers.exists(_.address == selfAddress)) versionedGossip
|
||||
else versionedGossip seen selfAddress
|
||||
|
||||
val newState = localState copy (latestGossip = seenVersionedGossip)
|
||||
|
||||
// if we won the race then update else try again
|
||||
if (!state.compareAndSet(localState, newState)) leaderActions() // recur
|
||||
else {
|
||||
// ----------------------
|
||||
// 8. Try to update the state with the new gossip
|
||||
// ----------------------
|
||||
if (!state.compareAndSet(localState, newState)) {
|
||||
|
||||
// ----------------------
|
||||
// 9. Failure - retry
|
||||
// ----------------------
|
||||
leaderActions() // recur
|
||||
|
||||
} else {
|
||||
// ----------------------
|
||||
// 10. Success - run all the side-effecting processing
|
||||
// ----------------------
|
||||
|
||||
// log the move of members from joining to up
|
||||
upMembers foreach { member ⇒ log.info("Cluster Node [{}] - Leader is moving node [{}] from JOINING to UP", selfAddress, member.address) }
|
||||
|
||||
// tell all removed members to remove and shut down themselves
|
||||
removedMembers foreach { member ⇒
|
||||
val address = member.address
|
||||
log.info("Cluster Node [{}] - Leader is moving node [{}] from EXITING to REMOVED - and removing node from node ring", selfAddress, address)
|
||||
clusterCommandConnectionFor(address) ! ClusterLeaderAction.Remove(address)
|
||||
}
|
||||
|
||||
// tell all exiting members to exit
|
||||
exitingMembers foreach { member ⇒
|
||||
val address = member.address
|
||||
log.info("Cluster Node [{}] - Leader is moving node [{}] from LEAVING to EXITING", selfAddress, address)
|
||||
clusterCommandConnectionFor(address) ! ClusterLeaderAction.Exit(address) // FIXME should use ? to await completion of handoff?
|
||||
}
|
||||
|
||||
// log the auto-downing of the unreachable nodes
|
||||
unreachableButNotDownedMembers foreach { member ⇒
|
||||
log.info("Cluster Node [{}] - Leader is marking unreachable node [{}] as DOWN", selfAddress, member.address)
|
||||
}
|
||||
|
||||
notifyMembershipChangeListeners(localState, newState)
|
||||
}
|
||||
}
|
||||
|
|
@ -1174,9 +1279,7 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
// Else we can't continue to check for convergence
|
||||
// When that is done we check that all the entries in the 'seen' table have the same vector clock version
|
||||
// and that all members exists in seen table
|
||||
val hasUnreachable = unreachable.nonEmpty && unreachable.exists { m ⇒
|
||||
m.status != Down && m.status != Removed
|
||||
}
|
||||
val hasUnreachable = unreachable.nonEmpty && unreachable.exists { _.status != Down }
|
||||
val allMembersInSeen = gossip.members.forall(m ⇒ seen.contains(m.address))
|
||||
|
||||
if (hasUnreachable) {
|
||||
|
|
@ -1205,14 +1308,18 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
|
||||
private def isUnavailable(state: State): Boolean = {
|
||||
val localGossip = state.latestGossip
|
||||
val localOverview = localGossip.overview
|
||||
val localMembers = localGossip.members
|
||||
val localUnreachableMembers = localOverview.unreachable
|
||||
val isUnreachable = localUnreachableMembers exists { _.address == selfAddress }
|
||||
val hasUnavailableMemberStatus = localMembers exists { m ⇒ (m == self) && m.status.isUnavailable }
|
||||
val isUnreachable = localGossip.overview.unreachable exists { _.address == selfAddress }
|
||||
val hasUnavailableMemberStatus = localGossip.members exists { m ⇒ (m == self) && m.status.isUnavailable }
|
||||
isUnreachable || hasUnavailableMemberStatus
|
||||
}
|
||||
|
||||
private def notifyMembershipChangeListeners(oldState: State, newState: State): Unit = {
|
||||
val oldMembersStatus = oldState.latestGossip.members.map(m ⇒ (m.address, m.status))
|
||||
val newMembersStatus = newState.latestGossip.members.map(m ⇒ (m.address, m.status))
|
||||
if (newMembersStatus != oldMembersStatus)
|
||||
newState.memberMembershipChangeListeners foreach { _ notify newState.latestGossip.members }
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks up and returns the local cluster command connection.
|
||||
*/
|
||||
|
|
@ -1235,9 +1342,9 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
addresses drop 1 take NrOfDeputyNodes filterNot (_ == selfAddress)
|
||||
|
||||
/**
|
||||
* INTERNAL API
|
||||
* INTERNAL API.
|
||||
*/
|
||||
private[akka] def selectRandomNode(addresses: IndexedSeq[Address]): Option[Address] =
|
||||
private[cluster] def selectRandomNode(addresses: IndexedSeq[Address]): Option[Address] =
|
||||
if (addresses.isEmpty) None
|
||||
else Some(addresses(ThreadLocalRandom.current nextInt addresses.size))
|
||||
|
||||
|
|
@ -1280,6 +1387,8 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
|
||||
def isAvailable: Boolean = clusterNode.isAvailable
|
||||
|
||||
def isRunning: Boolean = clusterNode.isRunning
|
||||
|
||||
// JMX commands
|
||||
|
||||
def join(address: String) = clusterNode.join(AddressFromURIString(address))
|
||||
|
|
@ -1287,10 +1396,6 @@ class Cluster(system: ExtendedActorSystem, val failureDetector: FailureDetector)
|
|||
def leave(address: String) = clusterNode.leave(AddressFromURIString(address))
|
||||
|
||||
def down(address: String) = clusterNode.down(AddressFromURIString(address))
|
||||
|
||||
def remove(address: String) = clusterNode.remove(AddressFromURIString(address))
|
||||
|
||||
def shutdown() = clusterNode.shutdown()
|
||||
}
|
||||
log.info("Cluster Node [{}] - registering cluster JMX MBean [{}]", selfAddress, clusterMBeanName)
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -47,23 +47,23 @@ abstract class ClientDowningNodeThatIsUnreachableSpec
|
|||
|
||||
// mark 'third' node as DOWN
|
||||
cluster.down(thirdAddress)
|
||||
testConductor.enter("down-third-node")
|
||||
enterBarrier("down-third-node")
|
||||
|
||||
awaitUpConvergence(numberOfMembers = 3, canNotBePartOfMemberRing = Seq(thirdAddress))
|
||||
cluster.latestGossip.members.exists(_.address == thirdAddress) must be(false)
|
||||
}
|
||||
|
||||
runOn(third) {
|
||||
testConductor.enter("down-third-node")
|
||||
enterBarrier("down-third-node")
|
||||
}
|
||||
|
||||
runOn(second, fourth) {
|
||||
testConductor.enter("down-third-node")
|
||||
enterBarrier("down-third-node")
|
||||
|
||||
awaitUpConvergence(numberOfMembers = 3, canNotBePartOfMemberRing = Seq(thirdAddress))
|
||||
}
|
||||
|
||||
testConductor.enter("await-completion")
|
||||
enterBarrier("await-completion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ abstract class ClientDowningNodeThatIsUpSpec
|
|||
runOn(first) {
|
||||
// mark 'third' node as DOWN
|
||||
cluster.down(thirdAddress)
|
||||
testConductor.enter("down-third-node")
|
||||
enterBarrier("down-third-node")
|
||||
|
||||
markNodeAsUnavailable(thirdAddress)
|
||||
|
||||
|
|
@ -52,16 +52,16 @@ abstract class ClientDowningNodeThatIsUpSpec
|
|||
}
|
||||
|
||||
runOn(third) {
|
||||
testConductor.enter("down-third-node")
|
||||
enterBarrier("down-third-node")
|
||||
}
|
||||
|
||||
runOn(second, fourth) {
|
||||
testConductor.enter("down-third-node")
|
||||
enterBarrier("down-third-node")
|
||||
|
||||
awaitUpConvergence(numberOfMembers = 3, canNotBePartOfMemberRing = Seq(thirdAddress))
|
||||
}
|
||||
|
||||
testConductor.enter("await-completion")
|
||||
enterBarrier("await-completion")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,12 +46,12 @@ abstract class ConvergenceSpec
|
|||
// doesn't join immediately
|
||||
}
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"not reach convergence while any nodes are unreachable" taggedAs LongRunningTest in {
|
||||
val thirdAddress = address(third)
|
||||
testConductor.enter("before-shutdown")
|
||||
enterBarrier("before-shutdown")
|
||||
|
||||
runOn(first) {
|
||||
// kill 'third' node
|
||||
|
|
@ -76,7 +76,7 @@ abstract class ConvergenceSpec
|
|||
}
|
||||
}
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
|
||||
"not move a new joining node to Up while there is no convergence" taggedAs LongRunningTest in {
|
||||
|
|
@ -110,7 +110,7 @@ abstract class ConvergenceSpec
|
|||
}
|
||||
}
|
||||
|
||||
testConductor.enter("after-3")
|
||||
enterBarrier("after-3")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ abstract class GossipingAccrualFailureDetectorSpec
|
|||
cluster.failureDetector.isAvailable(second) must be(true)
|
||||
cluster.failureDetector.isAvailable(third) must be(true)
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"mark node as 'unavailable' if a node in the cluster is shut down (and its heartbeats stops)" taggedAs LongRunningTest in {
|
||||
|
|
@ -55,7 +55,7 @@ abstract class GossipingAccrualFailureDetectorSpec
|
|||
cluster.failureDetector.isAvailable(second) must be(true)
|
||||
}
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ abstract class JoinTwoClustersSpec
|
|||
runOn(a1, b1, c1) {
|
||||
startClusterNode()
|
||||
}
|
||||
testConductor.enter("first-started")
|
||||
enterBarrier("first-started")
|
||||
|
||||
runOn(a1, a2) {
|
||||
cluster.join(a1)
|
||||
|
|
@ -58,7 +58,7 @@ abstract class JoinTwoClustersSpec
|
|||
assertLeader(b1, b2)
|
||||
assertLeader(c1, c2)
|
||||
|
||||
testConductor.enter("two-members")
|
||||
enterBarrier("two-members")
|
||||
|
||||
runOn(b2) {
|
||||
cluster.join(a1)
|
||||
|
|
@ -71,7 +71,7 @@ abstract class JoinTwoClustersSpec
|
|||
assertLeader(a1, a2, b1, b2)
|
||||
assertLeader(c1, c2)
|
||||
|
||||
testConductor.enter("four-members")
|
||||
enterBarrier("four-members")
|
||||
}
|
||||
|
||||
"be able to 'elect' a single leader after joining (C -> A + B)" taggedAs LongRunningTest in {
|
||||
|
|
@ -84,7 +84,7 @@ abstract class JoinTwoClustersSpec
|
|||
|
||||
assertLeader(a1, a2, b1, b2, c1, c2)
|
||||
|
||||
testConductor.enter("six-members")
|
||||
enterBarrier("six-members")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ abstract class LeaderDowningNodeThatIsUnreachableSpec
|
|||
runOn(first) {
|
||||
// kill 'fourth' node
|
||||
testConductor.shutdown(fourth, 0)
|
||||
testConductor.enter("down-fourth-node")
|
||||
enterBarrier("down-fourth-node")
|
||||
|
||||
// mark the node as unreachable in the failure detector
|
||||
markNodeAsUnavailable(fourthAddress)
|
||||
|
|
@ -57,26 +57,26 @@ abstract class LeaderDowningNodeThatIsUnreachableSpec
|
|||
}
|
||||
|
||||
runOn(fourth) {
|
||||
testConductor.enter("down-fourth-node")
|
||||
enterBarrier("down-fourth-node")
|
||||
}
|
||||
|
||||
runOn(second, third) {
|
||||
testConductor.enter("down-fourth-node")
|
||||
enterBarrier("down-fourth-node")
|
||||
|
||||
awaitUpConvergence(numberOfMembers = 3, canNotBePartOfMemberRing = Seq(fourthAddress), 30.seconds)
|
||||
}
|
||||
|
||||
testConductor.enter("await-completion-1")
|
||||
enterBarrier("await-completion-1")
|
||||
}
|
||||
|
||||
"be able to DOWN a 'middle' node that is UNREACHABLE" taggedAs LongRunningTest in {
|
||||
val secondAddress = address(second)
|
||||
|
||||
testConductor.enter("before-down-second-node")
|
||||
enterBarrier("before-down-second-node")
|
||||
runOn(first) {
|
||||
// kill 'second' node
|
||||
testConductor.shutdown(second, 0)
|
||||
testConductor.enter("down-second-node")
|
||||
enterBarrier("down-second-node")
|
||||
|
||||
// mark the node as unreachable in the failure detector
|
||||
markNodeAsUnavailable(secondAddress)
|
||||
|
|
@ -87,16 +87,16 @@ abstract class LeaderDowningNodeThatIsUnreachableSpec
|
|||
}
|
||||
|
||||
runOn(second) {
|
||||
testConductor.enter("down-second-node")
|
||||
enterBarrier("down-second-node")
|
||||
}
|
||||
|
||||
runOn(third) {
|
||||
testConductor.enter("down-second-node")
|
||||
enterBarrier("down-second-node")
|
||||
|
||||
awaitUpConvergence(numberOfMembers = 2, canNotBePartOfMemberRing = Seq(secondAddress), 30 seconds)
|
||||
}
|
||||
|
||||
testConductor.enter("await-completion-2")
|
||||
enterBarrier("await-completion-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ abstract class LeaderElectionSpec
|
|||
assertLeaderIn(sortedRoles)
|
||||
}
|
||||
|
||||
testConductor.enter("after")
|
||||
enterBarrier("after")
|
||||
}
|
||||
|
||||
def shutdownLeaderAndVerifyNewLeader(alreadyShutdown: Int): Unit = {
|
||||
|
|
@ -64,33 +64,33 @@ abstract class LeaderElectionSpec
|
|||
|
||||
case `controller` ⇒
|
||||
val leaderAddress = address(leader)
|
||||
testConductor.enter("before-shutdown")
|
||||
enterBarrier("before-shutdown")
|
||||
testConductor.shutdown(leader, 0)
|
||||
testConductor.enter("after-shutdown", "after-down", "completed")
|
||||
enterBarrier("after-shutdown", "after-down", "completed")
|
||||
markNodeAsUnavailable(leaderAddress)
|
||||
|
||||
case `leader` ⇒
|
||||
testConductor.enter("before-shutdown", "after-shutdown")
|
||||
enterBarrier("before-shutdown", "after-shutdown")
|
||||
// this node will be shutdown by the controller and doesn't participate in more barriers
|
||||
|
||||
case `aUser` ⇒
|
||||
val leaderAddress = address(leader)
|
||||
testConductor.enter("before-shutdown", "after-shutdown")
|
||||
enterBarrier("before-shutdown", "after-shutdown")
|
||||
// user marks the shutdown leader as DOWN
|
||||
cluster.down(leaderAddress)
|
||||
testConductor.enter("after-down", "completed")
|
||||
enterBarrier("after-down", "completed")
|
||||
markNodeAsUnavailable(leaderAddress)
|
||||
|
||||
case _ if remainingRoles.contains(myself) ⇒
|
||||
// remaining cluster nodes, not shutdown
|
||||
testConductor.enter("before-shutdown", "after-shutdown", "after-down")
|
||||
enterBarrier("before-shutdown", "after-shutdown", "after-down")
|
||||
|
||||
awaitUpConvergence(currentRoles.size - 1)
|
||||
val nextExpectedLeader = remainingRoles.head
|
||||
cluster.isLeader must be(myself == nextExpectedLeader)
|
||||
assertLeaderIn(remainingRoles)
|
||||
|
||||
testConductor.enter("completed")
|
||||
enterBarrier("completed")
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package akka.cluster
|
||||
|
||||
import scala.collection.immutable.SortedSet
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import akka.remote.testkit.MultiNodeConfig
|
||||
import akka.remote.testkit.MultiNodeSpec
|
||||
import akka.testkit._
|
||||
import akka.util.duration._
|
||||
|
||||
object LeaderLeavingMultiJvmSpec extends MultiNodeConfig {
|
||||
val first = role("first")
|
||||
val second = role("second")
|
||||
val third = role("third")
|
||||
|
||||
commonConfig(
|
||||
debugConfig(on = false)
|
||||
.withFallback(ConfigFactory.parseString("""
|
||||
akka.cluster {
|
||||
leader-actions-interval = 5 s # increase the leader action task frequency to make sure we get a chance to test the LEAVING state
|
||||
unreachable-nodes-reaper-interval = 30 s
|
||||
}""")
|
||||
.withFallback(MultiNodeClusterSpec.clusterConfig)))
|
||||
}
|
||||
|
||||
class LeaderLeavingMultiJvmNode1 extends LeaderLeavingSpec with FailureDetectorPuppetStrategy
|
||||
class LeaderLeavingMultiJvmNode2 extends LeaderLeavingSpec with FailureDetectorPuppetStrategy
|
||||
class LeaderLeavingMultiJvmNode3 extends LeaderLeavingSpec with FailureDetectorPuppetStrategy
|
||||
|
||||
abstract class LeaderLeavingSpec
|
||||
extends MultiNodeSpec(LeaderLeavingMultiJvmSpec)
|
||||
with MultiNodeClusterSpec {
|
||||
|
||||
import LeaderLeavingMultiJvmSpec._
|
||||
|
||||
"A LEADER that is LEAVING" must {
|
||||
|
||||
"be moved to LEAVING, then to EXITING, then to REMOVED, then be shut down and then a new LEADER should be elected" taggedAs LongRunningTest in {
|
||||
|
||||
awaitClusterUp(first, second, third)
|
||||
|
||||
val oldLeaderAddress = cluster.leader
|
||||
|
||||
if (cluster.isLeader) {
|
||||
|
||||
cluster.leave(oldLeaderAddress)
|
||||
enterBarrier("leader-left")
|
||||
|
||||
// verify that a NEW LEADER have taken over
|
||||
awaitCond(!cluster.isLeader)
|
||||
|
||||
// verify that the LEADER is shut down
|
||||
awaitCond(!cluster.isRunning, 30.seconds.dilated)
|
||||
|
||||
// verify that the LEADER is REMOVED
|
||||
awaitCond(cluster.status == MemberStatus.Removed)
|
||||
|
||||
} else {
|
||||
|
||||
enterBarrier("leader-left")
|
||||
|
||||
// verify that the LEADER is LEAVING
|
||||
awaitCond(cluster.latestGossip.members.exists(m ⇒ m.status == MemberStatus.Leaving && m.address == oldLeaderAddress)) // wait on LEAVING
|
||||
|
||||
// verify that the LEADER is EXITING
|
||||
awaitCond(cluster.latestGossip.members.exists(m ⇒ m.status == MemberStatus.Exiting && m.address == oldLeaderAddress)) // wait on EXITING
|
||||
|
||||
// verify that the LEADER is no longer part of the 'members' set
|
||||
awaitCond(cluster.latestGossip.members.forall(_.address != oldLeaderAddress))
|
||||
|
||||
// verify that the LEADER is not part of the 'unreachable' set
|
||||
awaitCond(cluster.latestGossip.overview.unreachable.forall(_.address != oldLeaderAddress))
|
||||
|
||||
// verify that we have a new LEADER
|
||||
awaitCond(cluster.leader != oldLeaderAddress)
|
||||
}
|
||||
|
||||
enterBarrier("finished")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -43,12 +43,12 @@ abstract class MembershipChangeListenerExitingSpec
|
|||
awaitClusterUp(first, second, third)
|
||||
|
||||
runOn(first) {
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
cluster.leave(second)
|
||||
}
|
||||
|
||||
runOn(second) {
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
}
|
||||
|
||||
runOn(third) {
|
||||
|
|
@ -59,11 +59,11 @@ abstract class MembershipChangeListenerExitingSpec
|
|||
exitingLatch.countDown()
|
||||
}
|
||||
})
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
exitingLatch.await
|
||||
}
|
||||
|
||||
testConductor.enter("finished")
|
||||
enterBarrier("finished")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,19 +42,19 @@ abstract class MembershipChangeListenerJoinSpec
|
|||
joinLatch.countDown()
|
||||
}
|
||||
})
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
|
||||
joinLatch.await
|
||||
}
|
||||
|
||||
runOn(second) {
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
cluster.join(first)
|
||||
}
|
||||
|
||||
awaitUpConvergence(2)
|
||||
|
||||
testConductor.enter("after")
|
||||
enterBarrier("after")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,12 +41,12 @@ abstract class MembershipChangeListenerLeavingSpec
|
|||
awaitClusterUp(first, second, third)
|
||||
|
||||
runOn(first) {
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
cluster.leave(second)
|
||||
}
|
||||
|
||||
runOn(second) {
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
}
|
||||
|
||||
runOn(third) {
|
||||
|
|
@ -59,11 +59,11 @@ abstract class MembershipChangeListenerLeavingSpec
|
|||
latch.countDown()
|
||||
}
|
||||
})
|
||||
testConductor.enter("registered-listener")
|
||||
enterBarrier("registered-listener")
|
||||
latch.await
|
||||
}
|
||||
|
||||
testConductor.enter("finished")
|
||||
enterBarrier("finished")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,16 +42,16 @@ abstract class MembershipChangeListenerUpSpec
|
|||
latch.countDown()
|
||||
}
|
||||
})
|
||||
testConductor.enter("listener-1-registered")
|
||||
enterBarrier("listener-1-registered")
|
||||
cluster.join(first)
|
||||
latch.await
|
||||
}
|
||||
|
||||
runOn(third) {
|
||||
testConductor.enter("listener-1-registered")
|
||||
enterBarrier("listener-1-registered")
|
||||
}
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"(when three nodes) after cluster convergence updates the membership table then all MembershipChangeListeners should be triggered" taggedAs LongRunningTest in {
|
||||
|
|
@ -64,7 +64,7 @@ abstract class MembershipChangeListenerUpSpec
|
|||
latch.countDown()
|
||||
}
|
||||
})
|
||||
testConductor.enter("listener-2-registered")
|
||||
enterBarrier("listener-2-registered")
|
||||
|
||||
runOn(third) {
|
||||
cluster.join(first)
|
||||
|
|
@ -72,7 +72,7 @@ abstract class MembershipChangeListenerUpSpec
|
|||
|
||||
latch.await
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,14 +115,14 @@ trait MultiNodeClusterSpec extends FailureDetectorStrategy with Suite { self: Mu
|
|||
// make sure that the node-to-join is started before other join
|
||||
startClusterNode()
|
||||
}
|
||||
testConductor.enter(roles.head.name + "-started")
|
||||
enterBarrier(roles.head.name + "-started")
|
||||
if (roles.tail.contains(myself)) {
|
||||
cluster.join(roles.head)
|
||||
}
|
||||
if (upConvergence && roles.contains(myself)) {
|
||||
awaitUpConvergence(numberOfMembers = roles.length)
|
||||
}
|
||||
testConductor.enter(roles.map(_.name).mkString("-") + "-joined")
|
||||
enterBarrier(roles.map(_.name).mkString("-") + "-joined")
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ abstract class NodeJoinSpec
|
|||
|
||||
awaitCond(cluster.latestGossip.members.exists { member ⇒ member.address == address(second) && member.status == MemberStatus.Joining })
|
||||
|
||||
testConductor.enter("after")
|
||||
enterBarrier("after")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,9 +18,9 @@ object NodeLeavingAndExitingAndBeingRemovedMultiJvmSpec extends MultiNodeConfig
|
|||
commonConfig(debugConfig(on = false).withFallback(MultiNodeClusterSpec.clusterConfig))
|
||||
}
|
||||
|
||||
class NodeLeavingAndExitingAndBeingRemovedMultiJvmNode1 extends NodeLeavingAndExitingAndBeingRemovedSpec with AccrualFailureDetectorStrategy
|
||||
class NodeLeavingAndExitingAndBeingRemovedMultiJvmNode2 extends NodeLeavingAndExitingAndBeingRemovedSpec with AccrualFailureDetectorStrategy
|
||||
class NodeLeavingAndExitingAndBeingRemovedMultiJvmNode3 extends NodeLeavingAndExitingAndBeingRemovedSpec with AccrualFailureDetectorStrategy
|
||||
class NodeLeavingAndExitingAndBeingRemovedMultiJvmNode1 extends NodeLeavingAndExitingAndBeingRemovedSpec with FailureDetectorPuppetStrategy
|
||||
class NodeLeavingAndExitingAndBeingRemovedMultiJvmNode2 extends NodeLeavingAndExitingAndBeingRemovedSpec with FailureDetectorPuppetStrategy
|
||||
class NodeLeavingAndExitingAndBeingRemovedMultiJvmNode3 extends NodeLeavingAndExitingAndBeingRemovedSpec with FailureDetectorPuppetStrategy
|
||||
|
||||
abstract class NodeLeavingAndExitingAndBeingRemovedSpec
|
||||
extends MultiNodeSpec(NodeLeavingAndExitingAndBeingRemovedMultiJvmSpec)
|
||||
|
|
@ -32,30 +32,30 @@ abstract class NodeLeavingAndExitingAndBeingRemovedSpec
|
|||
|
||||
"A node that is LEAVING a non-singleton cluster" must {
|
||||
|
||||
// FIXME make it work and remove ignore
|
||||
"be moved to EXITING and then to REMOVED by the reaper" taggedAs LongRunningTest ignore {
|
||||
"eventually set to REMOVED by the reaper, and removed from membership ring and seen table" taggedAs LongRunningTest in {
|
||||
|
||||
awaitClusterUp(first, second, third)
|
||||
|
||||
runOn(first) {
|
||||
cluster.leave(second)
|
||||
}
|
||||
testConductor.enter("second-left")
|
||||
enterBarrier("second-left")
|
||||
|
||||
runOn(first, third) {
|
||||
// verify that the 'second' node is no longer part of the 'members' set
|
||||
awaitCond(cluster.latestGossip.members.forall(_.address != address(second)), reaperWaitingTime)
|
||||
|
||||
// verify that the 'second' node is part of the 'unreachable' set
|
||||
awaitCond(cluster.latestGossip.overview.unreachable.exists(_.status == MemberStatus.Removed), reaperWaitingTime)
|
||||
|
||||
// verify node that got removed is 'second' node
|
||||
val isRemoved = cluster.latestGossip.overview.unreachable.find(_.status == MemberStatus.Removed)
|
||||
isRemoved must be('defined)
|
||||
isRemoved.get.address must be(address(second))
|
||||
// verify that the 'second' node is not part of the 'unreachable' set
|
||||
awaitCond(cluster.latestGossip.overview.unreachable.forall(_.address != address(second)), reaperWaitingTime)
|
||||
}
|
||||
|
||||
testConductor.enter("finished")
|
||||
runOn(second) {
|
||||
// verify that the second node is shut down and has status REMOVED
|
||||
awaitCond(!cluster.isRunning, reaperWaitingTime)
|
||||
awaitCond(cluster.status == MemberStatus.Removed, reaperWaitingTime)
|
||||
}
|
||||
|
||||
enterBarrier("finished")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,15 +38,14 @@ abstract class NodeLeavingAndExitingSpec
|
|||
|
||||
"A node that is LEAVING a non-singleton cluster" must {
|
||||
|
||||
// FIXME make it work and remove ignore
|
||||
"be moved to EXITING by the leader" taggedAs LongRunningTest ignore {
|
||||
"be moved to EXITING by the leader" taggedAs LongRunningTest in {
|
||||
|
||||
awaitClusterUp(first, second, third)
|
||||
|
||||
runOn(first) {
|
||||
cluster.leave(second)
|
||||
}
|
||||
testConductor.enter("second-left")
|
||||
enterBarrier("second-left")
|
||||
|
||||
runOn(first, third) {
|
||||
|
||||
|
|
@ -65,7 +64,7 @@ abstract class NodeLeavingAndExitingSpec
|
|||
hasExited.get.address must be(address(second))
|
||||
}
|
||||
|
||||
testConductor.enter("finished")
|
||||
enterBarrier("finished")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,15 +32,14 @@ abstract class NodeLeavingSpec
|
|||
|
||||
"A node that is LEAVING a non-singleton cluster" must {
|
||||
|
||||
// FIXME make it work and remove ignore
|
||||
"be marked as LEAVING in the converged membership table" taggedAs LongRunningTest ignore {
|
||||
"be marked as LEAVING in the converged membership table" taggedAs LongRunningTest in {
|
||||
|
||||
awaitClusterUp(first, second, third)
|
||||
|
||||
runOn(first) {
|
||||
cluster.leave(second)
|
||||
}
|
||||
testConductor.enter("second-left")
|
||||
enterBarrier("second-left")
|
||||
|
||||
runOn(first, third) {
|
||||
awaitCond(cluster.latestGossip.members.exists(_.status == MemberStatus.Leaving))
|
||||
|
|
@ -50,7 +49,7 @@ abstract class NodeLeavingSpec
|
|||
hasLeft.get.address must be(address(second))
|
||||
}
|
||||
|
||||
testConductor.enter("finished")
|
||||
enterBarrier("finished")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ abstract class NodeMembershipSpec
|
|||
runOn(first) {
|
||||
startClusterNode()
|
||||
}
|
||||
testConductor.enter("first-started")
|
||||
enterBarrier("first-started")
|
||||
|
||||
runOn(first, second) {
|
||||
cluster.join(first)
|
||||
|
|
@ -46,7 +46,7 @@ abstract class NodeMembershipSpec
|
|||
awaitCond(cluster.convergence.isDefined)
|
||||
}
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"(when three nodes) start gossiping to each other so that all nodes gets the same gossip info" taggedAs LongRunningTest in {
|
||||
|
|
@ -62,7 +62,7 @@ abstract class NodeMembershipSpec
|
|||
}
|
||||
awaitCond(cluster.convergence.isDefined)
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ abstract class NodeUpSpec
|
|||
|
||||
awaitClusterUp(first, second)
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"be unaffected when joining again" taggedAs LongRunningTest in {
|
||||
|
|
@ -45,12 +45,12 @@ abstract class NodeUpSpec
|
|||
unexpected.set(members)
|
||||
}
|
||||
})
|
||||
testConductor.enter("listener-registered")
|
||||
enterBarrier("listener-registered")
|
||||
|
||||
runOn(second) {
|
||||
cluster.join(first)
|
||||
}
|
||||
testConductor.enter("joined-again")
|
||||
enterBarrier("joined-again")
|
||||
|
||||
// let it run for a while to make sure that nothing bad happens
|
||||
for (n ← 1 to 20) {
|
||||
|
|
@ -59,7 +59,7 @@ abstract class NodeUpSpec
|
|||
cluster.latestGossip.members.forall(_.status == MemberStatus.Up) must be(true)
|
||||
}
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ abstract class SingletonClusterSpec
|
|||
cluster.isSingletonCluster must be(false)
|
||||
assertLeader(first, second)
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"become singleton cluster when one node is shutdown" taggedAs LongRunningTest in {
|
||||
|
|
@ -58,7 +58,7 @@ abstract class SingletonClusterSpec
|
|||
assertLeader(first)
|
||||
}
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,8 @@ object SunnyWeatherMultiJvmSpec extends MultiNodeConfig {
|
|||
commonConfig(ConfigFactory.parseString("""
|
||||
akka.cluster {
|
||||
nr-of-deputy-nodes = 0
|
||||
# FIXME remove this (use default) when ticket #2239 has been fixed
|
||||
gossip-interval = 400 ms
|
||||
}
|
||||
akka.loglevel = INFO
|
||||
"""))
|
||||
|
|
@ -61,7 +63,7 @@ abstract class SunnyWeatherSpec
|
|||
})
|
||||
|
||||
for (n ← 1 to 30) {
|
||||
testConductor.enter("period-" + n)
|
||||
enterBarrier("period-" + n)
|
||||
unexpected.get must be(null)
|
||||
awaitUpConvergence(roles.size)
|
||||
assertLeaderIn(roles)
|
||||
|
|
@ -69,7 +71,7 @@ abstract class SunnyWeatherSpec
|
|||
1.seconds.sleep
|
||||
}
|
||||
|
||||
testConductor.enter("after")
|
||||
enterBarrier("after")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -80,18 +80,18 @@ abstract class TransitionSpec
|
|||
gossipBarrierCounter += 1
|
||||
runOn(toRole) {
|
||||
val g = cluster.latestGossip
|
||||
testConductor.enter("before-gossip-" + gossipBarrierCounter)
|
||||
enterBarrier("before-gossip-" + gossipBarrierCounter)
|
||||
awaitCond(cluster.latestGossip != g) // received gossip
|
||||
testConductor.enter("after-gossip-" + gossipBarrierCounter)
|
||||
enterBarrier("after-gossip-" + gossipBarrierCounter)
|
||||
}
|
||||
runOn(fromRole) {
|
||||
testConductor.enter("before-gossip-" + gossipBarrierCounter)
|
||||
enterBarrier("before-gossip-" + gossipBarrierCounter)
|
||||
cluster.gossipTo(toRole) // send gossip
|
||||
testConductor.enter("after-gossip-" + gossipBarrierCounter)
|
||||
enterBarrier("after-gossip-" + gossipBarrierCounter)
|
||||
}
|
||||
runOn(roles.filterNot(r ⇒ r == fromRole || r == toRole): _*) {
|
||||
testConductor.enter("before-gossip-" + gossipBarrierCounter)
|
||||
testConductor.enter("after-gossip-" + gossipBarrierCounter)
|
||||
enterBarrier("before-gossip-" + gossipBarrierCounter)
|
||||
enterBarrier("after-gossip-" + gossipBarrierCounter)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -107,7 +107,7 @@ abstract class TransitionSpec
|
|||
cluster.leaderActions()
|
||||
cluster.status must be(Up)
|
||||
|
||||
testConductor.enter("after-1")
|
||||
enterBarrier("after-1")
|
||||
}
|
||||
|
||||
"perform correct transitions when second joining first" taggedAs LongRunningTest in {
|
||||
|
|
@ -121,7 +121,7 @@ abstract class TransitionSpec
|
|||
memberStatus(second) must be(Joining)
|
||||
cluster.convergence.isDefined must be(false)
|
||||
}
|
||||
testConductor.enter("second-joined")
|
||||
enterBarrier("second-joined")
|
||||
|
||||
first gossipTo second
|
||||
runOn(second) {
|
||||
|
|
@ -148,14 +148,14 @@ abstract class TransitionSpec
|
|||
memberStatus(second) must be(Joining)
|
||||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
testConductor.enter("convergence-joining-2")
|
||||
enterBarrier("convergence-joining-2")
|
||||
|
||||
runOn(leader(first, second)) {
|
||||
cluster.leaderActions()
|
||||
memberStatus(first) must be(Up)
|
||||
memberStatus(second) must be(Up)
|
||||
}
|
||||
testConductor.enter("leader-actions-2")
|
||||
enterBarrier("leader-actions-2")
|
||||
|
||||
leader(first, second) gossipTo nonLeader(first, second).head
|
||||
runOn(nonLeader(first, second).head) {
|
||||
|
|
@ -173,7 +173,7 @@ abstract class TransitionSpec
|
|||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
|
||||
testConductor.enter("after-2")
|
||||
enterBarrier("after-2")
|
||||
}
|
||||
|
||||
"perform correct transitions when third joins second" taggedAs LongRunningTest in {
|
||||
|
|
@ -187,7 +187,7 @@ abstract class TransitionSpec
|
|||
memberStatus(third) must be(Joining)
|
||||
seenLatestGossip must be(Set(second))
|
||||
}
|
||||
testConductor.enter("third-joined-second")
|
||||
enterBarrier("third-joined-second")
|
||||
|
||||
second gossipTo first
|
||||
runOn(first) {
|
||||
|
|
@ -231,7 +231,7 @@ abstract class TransitionSpec
|
|||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
|
||||
testConductor.enter("convergence-joining-3")
|
||||
enterBarrier("convergence-joining-3")
|
||||
|
||||
runOn(leader(first, second, third)) {
|
||||
cluster.leaderActions()
|
||||
|
|
@ -239,7 +239,7 @@ abstract class TransitionSpec
|
|||
memberStatus(second) must be(Up)
|
||||
memberStatus(third) must be(Up)
|
||||
}
|
||||
testConductor.enter("leader-actions-3")
|
||||
enterBarrier("leader-actions-3")
|
||||
|
||||
// leader gossipTo first non-leader
|
||||
leader(first, second, third) gossipTo nonLeader(first, second, third).head
|
||||
|
|
@ -278,7 +278,7 @@ abstract class TransitionSpec
|
|||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
|
||||
testConductor.enter("after-3")
|
||||
enterBarrier("after-3")
|
||||
}
|
||||
|
||||
"startup a second separated cluster consisting of nodes fourth and fifth" taggedAs LongRunningTest in {
|
||||
|
|
@ -296,9 +296,9 @@ abstract class TransitionSpec
|
|||
cluster.gossipTo(fourth)
|
||||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
testConductor.enter("fourth-joined-fifth")
|
||||
enterBarrier("fourth-joined-fifth")
|
||||
|
||||
testConductor.enter("after-4")
|
||||
enterBarrier("after-4")
|
||||
}
|
||||
|
||||
"perform correct transitions when second cluster (node fourth) joins first cluster (node third)" taggedAs LongRunningTest in {
|
||||
|
|
@ -310,7 +310,7 @@ abstract class TransitionSpec
|
|||
awaitMembers(first, second, third, fourth)
|
||||
seenLatestGossip must be(Set(third))
|
||||
}
|
||||
testConductor.enter("fourth-joined-third")
|
||||
enterBarrier("fourth-joined-third")
|
||||
|
||||
third gossipTo second
|
||||
runOn(second) {
|
||||
|
|
@ -362,7 +362,7 @@ abstract class TransitionSpec
|
|||
memberStatus(fifth) must be(Up)
|
||||
cluster.convergence.isDefined must be(true)
|
||||
|
||||
testConductor.enter("convergence-joining-3")
|
||||
enterBarrier("convergence-joining-3")
|
||||
|
||||
runOn(leader(roles: _*)) {
|
||||
cluster.leaderActions()
|
||||
|
|
@ -375,7 +375,7 @@ abstract class TransitionSpec
|
|||
x gossipTo y
|
||||
}
|
||||
|
||||
testConductor.enter("spread-5")
|
||||
enterBarrier("spread-5")
|
||||
|
||||
seenLatestGossip must be(roles.toSet)
|
||||
memberStatus(first) must be(Up)
|
||||
|
|
@ -385,7 +385,7 @@ abstract class TransitionSpec
|
|||
memberStatus(fifth) must be(Up)
|
||||
cluster.convergence.isDefined must be(true)
|
||||
|
||||
testConductor.enter("after-5")
|
||||
enterBarrier("after-5")
|
||||
}
|
||||
|
||||
"perform correct transitions when second becomes unavailble" taggedAs LongRunningTest in {
|
||||
|
|
@ -429,7 +429,7 @@ abstract class TransitionSpec
|
|||
cluster.convergence.isDefined must be(true)
|
||||
}
|
||||
|
||||
testConductor.enter("after-6")
|
||||
enterBarrier("after-6")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.cluster
|
||||
|
||||
import akka.actor.{ Address, AddressFromURIString }
|
||||
import java.net.InetSocketAddress
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import org.scalatest.WordSpec
|
||||
import scala.collection.immutable.SortedSet
|
||||
import scala.util.Random
|
||||
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class MemberOrderingSpec extends WordSpec with MustMatchers {
|
||||
import Member.ordering
|
||||
import Member.addressOrdering
|
||||
import MemberStatus._
|
||||
|
||||
"An Ordering[Member]" must {
|
||||
|
||||
"order non-exiting members by host:port" in {
|
||||
val members = SortedSet.empty[Member] +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1112"), Up) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1113"), Joining) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1111"), Up)
|
||||
|
||||
val seq = members.toSeq
|
||||
seq.size must equal(3)
|
||||
seq(0) must equal(Member(AddressFromURIString("akka://sys@darkstar:1111"), Up))
|
||||
seq(1) must equal(Member(AddressFromURIString("akka://sys@darkstar:1112"), Up))
|
||||
seq(2) must equal(Member(AddressFromURIString("akka://sys@darkstar:1113"), Joining))
|
||||
}
|
||||
|
||||
"order exiting members by last" in {
|
||||
val members = SortedSet.empty[Member] +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1112"), Exiting) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1113"), Up) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1111"), Joining)
|
||||
|
||||
val seq = members.toSeq
|
||||
seq.size must equal(3)
|
||||
seq(0) must equal(Member(AddressFromURIString("akka://sys@darkstar:1111"), Joining))
|
||||
seq(1) must equal(Member(AddressFromURIString("akka://sys@darkstar:1113"), Up))
|
||||
seq(2) must equal(Member(AddressFromURIString("akka://sys@darkstar:1112"), Exiting))
|
||||
}
|
||||
|
||||
"order multiple exiting members by last but internally by host:port" in {
|
||||
val members = SortedSet.empty[Member] +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1112"), Exiting) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1113"), Leaving) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1111"), Up) +
|
||||
Member(AddressFromURIString("akka://sys@darkstar:1110"), Exiting)
|
||||
|
||||
val seq = members.toSeq
|
||||
seq.size must equal(4)
|
||||
seq(0) must equal(Member(AddressFromURIString("akka://sys@darkstar:1111"), Up))
|
||||
seq(1) must equal(Member(AddressFromURIString("akka://sys@darkstar:1113"), Leaving))
|
||||
seq(2) must equal(Member(AddressFromURIString("akka://sys@darkstar:1110"), Exiting))
|
||||
seq(3) must equal(Member(AddressFromURIString("akka://sys@darkstar:1112"), Exiting))
|
||||
}
|
||||
|
||||
"be sorted by address correctly" in {
|
||||
import Member.ordering
|
||||
// sorting should be done on host and port, only
|
||||
val m1 = Member(Address("akka", "sys1", "host1", 9000), MemberStatus.Up)
|
||||
val m2 = Member(Address("akka", "sys1", "host1", 10000), MemberStatus.Up)
|
||||
val m3 = Member(Address("cluster", "sys2", "host2", 8000), MemberStatus.Up)
|
||||
val m4 = Member(Address("cluster", "sys2", "host2", 9000), MemberStatus.Up)
|
||||
val m5 = Member(Address("cluster", "sys1", "host2", 10000), MemberStatus.Up)
|
||||
|
||||
val expected = IndexedSeq(m1, m2, m3, m4, m5)
|
||||
val shuffled = Random.shuffle(expected)
|
||||
shuffled.sorted must be(expected)
|
||||
(SortedSet.empty[Member] ++ shuffled).toIndexedSeq must be(expected)
|
||||
}
|
||||
|
||||
"have stable equals and hashCode" in {
|
||||
val m1 = Member(Address("akka", "sys1", "host1", 9000), MemberStatus.Joining)
|
||||
val m2 = Member(Address("akka", "sys1", "host1", 9000), MemberStatus.Up)
|
||||
val m3 = Member(Address("akka", "sys1", "host1", 10000), MemberStatus.Up)
|
||||
|
||||
m1 must be(m2)
|
||||
m1.hashCode must be(m2.hashCode)
|
||||
|
||||
m3 must not be (m2)
|
||||
m3 must not be (m1)
|
||||
}
|
||||
}
|
||||
|
||||
"An Ordering[Address]" must {
|
||||
|
||||
"order addresses by port" in {
|
||||
val addresses = SortedSet.empty[Address] +
|
||||
AddressFromURIString("akka://sys@darkstar:1112") +
|
||||
AddressFromURIString("akka://sys@darkstar:1113") +
|
||||
AddressFromURIString("akka://sys@darkstar:1110") +
|
||||
AddressFromURIString("akka://sys@darkstar:1111")
|
||||
|
||||
val seq = addresses.toSeq
|
||||
seq.size must equal(4)
|
||||
seq(0) must equal(AddressFromURIString("akka://sys@darkstar:1110"))
|
||||
seq(1) must equal(AddressFromURIString("akka://sys@darkstar:1111"))
|
||||
seq(2) must equal(AddressFromURIString("akka://sys@darkstar:1112"))
|
||||
seq(3) must equal(AddressFromURIString("akka://sys@darkstar:1113"))
|
||||
}
|
||||
|
||||
"order addresses by hostname" in {
|
||||
val addresses = SortedSet.empty[Address] +
|
||||
AddressFromURIString("akka://sys@darkstar2:1110") +
|
||||
AddressFromURIString("akka://sys@darkstar1:1110") +
|
||||
AddressFromURIString("akka://sys@darkstar3:1110") +
|
||||
AddressFromURIString("akka://sys@darkstar0:1110")
|
||||
|
||||
val seq = addresses.toSeq
|
||||
seq.size must equal(4)
|
||||
seq(0) must equal(AddressFromURIString("akka://sys@darkstar0:1110"))
|
||||
seq(1) must equal(AddressFromURIString("akka://sys@darkstar1:1110"))
|
||||
seq(2) must equal(AddressFromURIString("akka://sys@darkstar2:1110"))
|
||||
seq(3) must equal(AddressFromURIString("akka://sys@darkstar3:1110"))
|
||||
}
|
||||
|
||||
"order addresses by hostname and port" in {
|
||||
val addresses = SortedSet.empty[Address] +
|
||||
AddressFromURIString("akka://sys@darkstar2:1110") +
|
||||
AddressFromURIString("akka://sys@darkstar0:1111") +
|
||||
AddressFromURIString("akka://sys@darkstar2:1111") +
|
||||
AddressFromURIString("akka://sys@darkstar0:1110")
|
||||
|
||||
val seq = addresses.toSeq
|
||||
seq.size must equal(4)
|
||||
seq(0) must equal(AddressFromURIString("akka://sys@darkstar0:1110"))
|
||||
seq(1) must equal(AddressFromURIString("akka://sys@darkstar0:1111"))
|
||||
seq(2) must equal(AddressFromURIString("akka://sys@darkstar2:1110"))
|
||||
seq(3) must equal(AddressFromURIString("akka://sys@darkstar2:1111"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
|
||||
package akka.cluster
|
||||
|
||||
import org.scalatest.WordSpec
|
||||
import org.scalatest.matchers.MustMatchers
|
||||
import akka.actor.Address
|
||||
import scala.util.Random
|
||||
import scala.collection.immutable.SortedSet
|
||||
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class MemberSpec extends WordSpec with MustMatchers {
|
||||
|
||||
"Member" must {
|
||||
|
||||
"be sorted by address correctly" in {
|
||||
import Member.ordering
|
||||
// sorting should be done on host and port, only
|
||||
val m1 = Member(Address("akka", "sys1", "host1", 9000), MemberStatus.Up)
|
||||
val m2 = Member(Address("akka", "sys1", "host1", 10000), MemberStatus.Up)
|
||||
val m3 = Member(Address("cluster", "sys2", "host2", 8000), MemberStatus.Up)
|
||||
val m4 = Member(Address("cluster", "sys2", "host2", 9000), MemberStatus.Up)
|
||||
val m5 = Member(Address("cluster", "sys1", "host2", 10000), MemberStatus.Up)
|
||||
|
||||
val expected = IndexedSeq(m1, m2, m3, m4, m5)
|
||||
val shuffled = Random.shuffle(expected)
|
||||
shuffled.sorted must be(expected)
|
||||
(SortedSet.empty[Member] ++ shuffled).toIndexedSeq must be(expected)
|
||||
}
|
||||
|
||||
"have stable equals and hashCode" in {
|
||||
val m1 = Member(Address("akka", "sys1", "host1", 9000), MemberStatus.Joining)
|
||||
val m2 = Member(Address("akka", "sys1", "host1", 9000), MemberStatus.Up)
|
||||
val m3 = Member(Address("akka", "sys1", "host1", 10000), MemberStatus.Up)
|
||||
|
||||
m1 must be(m2)
|
||||
m1.hashCode must be(m2.hashCode)
|
||||
|
||||
m3 must not be (m2)
|
||||
m3 must not be (m1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,7 @@
|
|||
Cluster Specification
|
||||
######################
|
||||
|
||||
.. note:: *This document describes the new clustering coming in Akka Coltrane and
|
||||
is not available in the latest stable release)*
|
||||
.. note:: *This document describes the new clustering coming in Akka Coltrane and is not available in the latest stable release)*
|
||||
|
||||
Intro
|
||||
=====
|
||||
|
|
@ -164,8 +163,8 @@ After gossip convergence a ``leader`` for the cluster can be determined. There i
|
|||
``leader`` election process, the ``leader`` can always be recognised deterministically
|
||||
by any node whenever there is gossip convergence. The ``leader`` is simply the first
|
||||
node in sorted order that is able to take the leadership role, where the only
|
||||
allowed member states for a ``leader`` are ``up`` or ``leaving`` (see below for more
|
||||
information about member states).
|
||||
allowed member states for a ``leader`` are ``up``, ``leaving`` or ``exiting`` (see
|
||||
below for more information about member states).
|
||||
|
||||
The role of the ``leader`` is to shift members in and out of the cluster, changing
|
||||
``joining`` members to the ``up`` state or ``exiting`` members to the
|
||||
|
|
@ -302,10 +301,6 @@ handoff has completed then the node will change to the ``exiting`` state. Once
|
|||
all nodes have seen the exiting state (convergence) the ``leader`` will remove the
|
||||
node from the cluster, marking it as ``removed``.
|
||||
|
||||
A node can also be removed forcefully by moving it directly to the ``removed``
|
||||
state using the ``remove`` action. The cluster will rebalance based on the new
|
||||
cluster membership.
|
||||
|
||||
If a node is unreachable then gossip convergence is not possible and therefore
|
||||
any ``leader`` actions are also not possible (for instance, allowing a node to
|
||||
become a part of the cluster, or changing actor distribution). To be able to
|
||||
|
|
@ -314,11 +309,12 @@ unreachable node is experiencing only transient difficulties then it can be
|
|||
explicitly marked as ``down`` using the ``down`` user action. When this node
|
||||
comes back up and begins gossiping it will automatically go through the joining
|
||||
process again. If the unreachable node will be permanently down then it can be
|
||||
removed from the cluster directly with the ``remove`` user action. The cluster
|
||||
can also *auto-down* a node using the accrual failure detector.
|
||||
removed from the cluster directly by shutting the actor system down or killing it
|
||||
through an external ``SIGKILL`` signal, invocation of ``System.exit(status)`` or
|
||||
similar. The cluster can, through the leader, also *auto-down* a node.
|
||||
|
||||
This means that nodes can join and leave the cluster at any point in time,
|
||||
e.g. provide cluster elasticity.
|
||||
This means that nodes can join and leave the cluster at any point in time, i.e.
|
||||
provide cluster elasticity.
|
||||
|
||||
|
||||
State Diagram for the Member States
|
||||
|
|
@ -339,12 +335,12 @@ Member States
|
|||
- **leaving** / **exiting**
|
||||
states during graceful removal
|
||||
|
||||
- **removed**
|
||||
tombstone state (no longer a member)
|
||||
|
||||
- **down**
|
||||
marked as down/offline/unreachable
|
||||
|
||||
- **removed**
|
||||
tombstone state (no longer a member)
|
||||
|
||||
|
||||
User Actions
|
||||
^^^^^^^^^^^^
|
||||
|
|
@ -359,9 +355,6 @@ User Actions
|
|||
- **down**
|
||||
mark a node as temporarily down
|
||||
|
||||
- **remove**
|
||||
remove a node from the cluster immediately
|
||||
|
||||
|
||||
Leader Actions
|
||||
^^^^^^^^^^^^^^
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ which is a user-level concern.
|
|||
Ordering is preserved on a per-sender basis
|
||||
-------------------------------------------
|
||||
|
||||
Actor ``A1` sends messages ``M1``, ``M2``, ``M3`` to ``A2``
|
||||
Actor ``A1`` sends messages ``M1``, ``M2``, ``M3`` to ``A2``
|
||||
Actor ``A3`` sends messages ``M4``, ``M5``, ``M6`` to ``A2``
|
||||
|
||||
This means that:
|
||||
|
|
|
|||
29
akka-kernel/src/main/dist/bin/akka-cluster
vendored
29
akka-kernel/src/main/dist/bin/akka-cluster
vendored
|
|
@ -63,20 +63,6 @@ case "$2" in
|
|||
$JMX_CLIENT $HOST akka:type=Cluster leave=$ACTOR_SYSTEM_URL
|
||||
;;
|
||||
|
||||
remove)
|
||||
if [ $# -ne 3 ]; then
|
||||
echo "Usage: $SELF <node-hostname:jmx-port> remove <actor-system-url-to-join>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ensureNodeIsRunningAndAvailable
|
||||
shift
|
||||
|
||||
ACTOR_SYSTEM_URL=$2
|
||||
echo "Scheduling $ACTOR_SYSTEM_URL to REMOVE"
|
||||
$JMX_CLIENT $HOST akka:type=Cluster remove=$ACTOR_SYSTEM_URL
|
||||
;;
|
||||
|
||||
down)
|
||||
if [ $# -ne 3 ]; then
|
||||
echo "Usage: $SELF <node-hostname:jmx-port> down <actor-system-url-to-join>"
|
||||
|
|
@ -169,19 +155,32 @@ case "$2" in
|
|||
$JMX_CLIENT $HOST akka:type=Cluster Available
|
||||
;;
|
||||
|
||||
is-running)
|
||||
if [ $# -ne 2 ]; then
|
||||
echo "Usage: $SELF <node-hostname:jmx-port> is-running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ensureNodeIsRunningAndAvailable
|
||||
shift
|
||||
|
||||
echo "Checking if member node on $HOST is AVAILABLE"
|
||||
$JMX_CLIENT $HOST akka:type=Cluster Running
|
||||
;;
|
||||
|
||||
*)
|
||||
printf "Usage: bin/$SELF <node-hostname:jmx-port> <command> ...\n"
|
||||
printf "\n"
|
||||
printf "Supported commands are:\n"
|
||||
printf "%26s - %s\n" "join <actor-system-url>" "Sends request a JOIN node with the specified URL"
|
||||
printf "%26s - %s\n" "leave <actor-system-url>" "Sends a request for node with URL to LEAVE the cluster"
|
||||
printf "%26s - %s\n" "remove <actor-system-url>" "Sends a request for node with URL to be instantly REMOVED from the cluster"
|
||||
printf "%26s - %s\n" "down <actor-system-url>" "Sends a request for marking node with URL as DOWN"
|
||||
printf "%26s - %s\n" member-status "Asks the member node for its current status"
|
||||
printf "%26s - %s\n" cluster-status "Asks the cluster for its current status (member ring, unavailable nodes, meta data etc.)"
|
||||
printf "%26s - %s\n" leader "Asks the cluster who the current leader is"
|
||||
printf "%26s - %s\n" is-singleton "Checks if the cluster is a singleton cluster (single node cluster)"
|
||||
printf "%26s - %s\n" is-available "Checks if the member node is available"
|
||||
printf "%26s - %s\n" is-running "Checks if the member node is running"
|
||||
printf "%26s - %s\n" has-convergence "Checks if there is a cluster convergence"
|
||||
printf "Where the <actor-system-url> should be on the format of 'akka://actor-system-name@hostname:port'\n"
|
||||
printf "\n"
|
||||
|
|
|
|||
|
|
@ -8,6 +8,81 @@ public final class TestConductorProtocol {
|
|||
public static void registerAllExtensions(
|
||||
com.google.protobuf.ExtensionRegistry registry) {
|
||||
}
|
||||
public enum BarrierOp
|
||||
implements com.google.protobuf.ProtocolMessageEnum {
|
||||
Enter(0, 1),
|
||||
Fail(1, 2),
|
||||
Succeeded(2, 3),
|
||||
Failed(3, 4),
|
||||
;
|
||||
|
||||
public static final int Enter_VALUE = 1;
|
||||
public static final int Fail_VALUE = 2;
|
||||
public static final int Succeeded_VALUE = 3;
|
||||
public static final int Failed_VALUE = 4;
|
||||
|
||||
|
||||
public final int getNumber() { return value; }
|
||||
|
||||
public static BarrierOp valueOf(int value) {
|
||||
switch (value) {
|
||||
case 1: return Enter;
|
||||
case 2: return Fail;
|
||||
case 3: return Succeeded;
|
||||
case 4: return Failed;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static com.google.protobuf.Internal.EnumLiteMap<BarrierOp>
|
||||
internalGetValueMap() {
|
||||
return internalValueMap;
|
||||
}
|
||||
private static com.google.protobuf.Internal.EnumLiteMap<BarrierOp>
|
||||
internalValueMap =
|
||||
new com.google.protobuf.Internal.EnumLiteMap<BarrierOp>() {
|
||||
public BarrierOp findValueByNumber(int number) {
|
||||
return BarrierOp.valueOf(number);
|
||||
}
|
||||
};
|
||||
|
||||
public final com.google.protobuf.Descriptors.EnumValueDescriptor
|
||||
getValueDescriptor() {
|
||||
return getDescriptor().getValues().get(index);
|
||||
}
|
||||
public final com.google.protobuf.Descriptors.EnumDescriptor
|
||||
getDescriptorForType() {
|
||||
return getDescriptor();
|
||||
}
|
||||
public static final com.google.protobuf.Descriptors.EnumDescriptor
|
||||
getDescriptor() {
|
||||
return akka.remote.testconductor.TestConductorProtocol.getDescriptor().getEnumTypes().get(0);
|
||||
}
|
||||
|
||||
private static final BarrierOp[] VALUES = {
|
||||
Enter, Fail, Succeeded, Failed,
|
||||
};
|
||||
|
||||
public static BarrierOp valueOf(
|
||||
com.google.protobuf.Descriptors.EnumValueDescriptor desc) {
|
||||
if (desc.getType() != getDescriptor()) {
|
||||
throw new java.lang.IllegalArgumentException(
|
||||
"EnumValueDescriptor is not for this type.");
|
||||
}
|
||||
return VALUES[desc.getIndex()];
|
||||
}
|
||||
|
||||
private final int index;
|
||||
private final int value;
|
||||
|
||||
private BarrierOp(int index, int value) {
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
// @@protoc_insertion_point(enum_scope:BarrierOp)
|
||||
}
|
||||
|
||||
public enum FailType
|
||||
implements com.google.protobuf.ProtocolMessageEnum {
|
||||
Throttle(0, 1),
|
||||
|
|
@ -56,7 +131,7 @@ public final class TestConductorProtocol {
|
|||
}
|
||||
public static final com.google.protobuf.Descriptors.EnumDescriptor
|
||||
getDescriptor() {
|
||||
return akka.remote.testconductor.TestConductorProtocol.getDescriptor().getEnumTypes().get(0);
|
||||
return akka.remote.testconductor.TestConductorProtocol.getDescriptor().getEnumTypes().get(1);
|
||||
}
|
||||
|
||||
private static final FailType[] VALUES = {
|
||||
|
|
@ -128,7 +203,7 @@ public final class TestConductorProtocol {
|
|||
}
|
||||
public static final com.google.protobuf.Descriptors.EnumDescriptor
|
||||
getDescriptor() {
|
||||
return akka.remote.testconductor.TestConductorProtocol.getDescriptor().getEnumTypes().get(1);
|
||||
return akka.remote.testconductor.TestConductorProtocol.getDescriptor().getEnumTypes().get(2);
|
||||
}
|
||||
|
||||
private static final Direction[] VALUES = {
|
||||
|
|
@ -1699,9 +1774,13 @@ public final class TestConductorProtocol {
|
|||
boolean hasName();
|
||||
String getName();
|
||||
|
||||
// optional bool status = 2;
|
||||
boolean hasStatus();
|
||||
boolean getStatus();
|
||||
// required .BarrierOp op = 2;
|
||||
boolean hasOp();
|
||||
akka.remote.testconductor.TestConductorProtocol.BarrierOp getOp();
|
||||
|
||||
// optional int64 timeout = 3;
|
||||
boolean hasTimeout();
|
||||
long getTimeout();
|
||||
}
|
||||
public static final class EnterBarrier extends
|
||||
com.google.protobuf.GeneratedMessage
|
||||
|
|
@ -1764,19 +1843,30 @@ public final class TestConductorProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
// optional bool status = 2;
|
||||
public static final int STATUS_FIELD_NUMBER = 2;
|
||||
private boolean status_;
|
||||
public boolean hasStatus() {
|
||||
// required .BarrierOp op = 2;
|
||||
public static final int OP_FIELD_NUMBER = 2;
|
||||
private akka.remote.testconductor.TestConductorProtocol.BarrierOp op_;
|
||||
public boolean hasOp() {
|
||||
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||
}
|
||||
public boolean getStatus() {
|
||||
return status_;
|
||||
public akka.remote.testconductor.TestConductorProtocol.BarrierOp getOp() {
|
||||
return op_;
|
||||
}
|
||||
|
||||
// optional int64 timeout = 3;
|
||||
public static final int TIMEOUT_FIELD_NUMBER = 3;
|
||||
private long timeout_;
|
||||
public boolean hasTimeout() {
|
||||
return ((bitField0_ & 0x00000004) == 0x00000004);
|
||||
}
|
||||
public long getTimeout() {
|
||||
return timeout_;
|
||||
}
|
||||
|
||||
private void initFields() {
|
||||
name_ = "";
|
||||
status_ = false;
|
||||
op_ = akka.remote.testconductor.TestConductorProtocol.BarrierOp.Enter;
|
||||
timeout_ = 0L;
|
||||
}
|
||||
private byte memoizedIsInitialized = -1;
|
||||
public final boolean isInitialized() {
|
||||
|
|
@ -1787,6 +1877,10 @@ public final class TestConductorProtocol {
|
|||
memoizedIsInitialized = 0;
|
||||
return false;
|
||||
}
|
||||
if (!hasOp()) {
|
||||
memoizedIsInitialized = 0;
|
||||
return false;
|
||||
}
|
||||
memoizedIsInitialized = 1;
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1798,7 +1892,10 @@ public final class TestConductorProtocol {
|
|||
output.writeBytes(1, getNameBytes());
|
||||
}
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
output.writeBool(2, status_);
|
||||
output.writeEnum(2, op_.getNumber());
|
||||
}
|
||||
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
output.writeInt64(3, timeout_);
|
||||
}
|
||||
getUnknownFields().writeTo(output);
|
||||
}
|
||||
|
|
@ -1815,7 +1912,11 @@ public final class TestConductorProtocol {
|
|||
}
|
||||
if (((bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeBoolSize(2, status_);
|
||||
.computeEnumSize(2, op_.getNumber());
|
||||
}
|
||||
if (((bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
size += com.google.protobuf.CodedOutputStream
|
||||
.computeInt64Size(3, timeout_);
|
||||
}
|
||||
size += getUnknownFields().getSerializedSize();
|
||||
memoizedSerializedSize = size;
|
||||
|
|
@ -1943,8 +2044,10 @@ public final class TestConductorProtocol {
|
|||
super.clear();
|
||||
name_ = "";
|
||||
bitField0_ = (bitField0_ & ~0x00000001);
|
||||
status_ = false;
|
||||
op_ = akka.remote.testconductor.TestConductorProtocol.BarrierOp.Enter;
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
timeout_ = 0L;
|
||||
bitField0_ = (bitField0_ & ~0x00000004);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
|
@ -1990,7 +2093,11 @@ public final class TestConductorProtocol {
|
|||
if (((from_bitField0_ & 0x00000002) == 0x00000002)) {
|
||||
to_bitField0_ |= 0x00000002;
|
||||
}
|
||||
result.status_ = status_;
|
||||
result.op_ = op_;
|
||||
if (((from_bitField0_ & 0x00000004) == 0x00000004)) {
|
||||
to_bitField0_ |= 0x00000004;
|
||||
}
|
||||
result.timeout_ = timeout_;
|
||||
result.bitField0_ = to_bitField0_;
|
||||
onBuilt();
|
||||
return result;
|
||||
|
|
@ -2010,8 +2117,11 @@ public final class TestConductorProtocol {
|
|||
if (other.hasName()) {
|
||||
setName(other.getName());
|
||||
}
|
||||
if (other.hasStatus()) {
|
||||
setStatus(other.getStatus());
|
||||
if (other.hasOp()) {
|
||||
setOp(other.getOp());
|
||||
}
|
||||
if (other.hasTimeout()) {
|
||||
setTimeout(other.getTimeout());
|
||||
}
|
||||
this.mergeUnknownFields(other.getUnknownFields());
|
||||
return this;
|
||||
|
|
@ -2022,6 +2132,10 @@ public final class TestConductorProtocol {
|
|||
|
||||
return false;
|
||||
}
|
||||
if (!hasOp()) {
|
||||
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -2054,8 +2168,19 @@ public final class TestConductorProtocol {
|
|||
break;
|
||||
}
|
||||
case 16: {
|
||||
int rawValue = input.readEnum();
|
||||
akka.remote.testconductor.TestConductorProtocol.BarrierOp value = akka.remote.testconductor.TestConductorProtocol.BarrierOp.valueOf(rawValue);
|
||||
if (value == null) {
|
||||
unknownFields.mergeVarintField(2, rawValue);
|
||||
} else {
|
||||
bitField0_ |= 0x00000002;
|
||||
status_ = input.readBool();
|
||||
op_ = value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 24: {
|
||||
bitField0_ |= 0x00000004;
|
||||
timeout_ = input.readInt64();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -2100,23 +2225,47 @@ public final class TestConductorProtocol {
|
|||
onChanged();
|
||||
}
|
||||
|
||||
// optional bool status = 2;
|
||||
private boolean status_ ;
|
||||
public boolean hasStatus() {
|
||||
// required .BarrierOp op = 2;
|
||||
private akka.remote.testconductor.TestConductorProtocol.BarrierOp op_ = akka.remote.testconductor.TestConductorProtocol.BarrierOp.Enter;
|
||||
public boolean hasOp() {
|
||||
return ((bitField0_ & 0x00000002) == 0x00000002);
|
||||
}
|
||||
public boolean getStatus() {
|
||||
return status_;
|
||||
public akka.remote.testconductor.TestConductorProtocol.BarrierOp getOp() {
|
||||
return op_;
|
||||
}
|
||||
public Builder setOp(akka.remote.testconductor.TestConductorProtocol.BarrierOp value) {
|
||||
if (value == null) {
|
||||
throw new NullPointerException();
|
||||
}
|
||||
public Builder setStatus(boolean value) {
|
||||
bitField0_ |= 0x00000002;
|
||||
status_ = value;
|
||||
op_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearStatus() {
|
||||
public Builder clearOp() {
|
||||
bitField0_ = (bitField0_ & ~0x00000002);
|
||||
status_ = false;
|
||||
op_ = akka.remote.testconductor.TestConductorProtocol.BarrierOp.Enter;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
|
||||
// optional int64 timeout = 3;
|
||||
private long timeout_ ;
|
||||
public boolean hasTimeout() {
|
||||
return ((bitField0_ & 0x00000004) == 0x00000004);
|
||||
}
|
||||
public long getTimeout() {
|
||||
return timeout_;
|
||||
}
|
||||
public Builder setTimeout(long value) {
|
||||
bitField0_ |= 0x00000004;
|
||||
timeout_ = value;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
public Builder clearTimeout() {
|
||||
bitField0_ = (bitField0_ & ~0x00000004);
|
||||
timeout_ = 0L;
|
||||
onChanged();
|
||||
return this;
|
||||
}
|
||||
|
|
@ -4056,19 +4205,21 @@ public final class TestConductorProtocol {
|
|||
"\0132\r.EnterBarrier\022\037\n\007failure\030\003 \001(\0132\016.Inje" +
|
||||
"ctFailure\022\014\n\004done\030\004 \001(\t\022\035\n\004addr\030\005 \001(\0132\017." +
|
||||
"AddressRequest\"0\n\005Hello\022\014\n\004name\030\001 \002(\t\022\031\n" +
|
||||
"\007address\030\002 \002(\0132\010.Address\",\n\014EnterBarrier" +
|
||||
"\022\014\n\004name\030\001 \002(\t\022\016\n\006status\030\002 \001(\010\"6\n\016Addres" +
|
||||
"sRequest\022\014\n\004node\030\001 \002(\t\022\026\n\004addr\030\002 \001(\0132\010.A" +
|
||||
"ddress\"G\n\007Address\022\020\n\010protocol\030\001 \002(\t\022\016\n\006s" +
|
||||
"ystem\030\002 \002(\t\022\014\n\004host\030\003 \002(\t\022\014\n\004port\030\004 \002(\005\"",
|
||||
"\212\001\n\rInjectFailure\022\032\n\007failure\030\001 \002(\0162\t.Fai" +
|
||||
"lType\022\035\n\tdirection\030\002 \001(\0162\n.Direction\022\031\n\007" +
|
||||
"address\030\003 \001(\0132\010.Address\022\020\n\010rateMBit\030\006 \001(" +
|
||||
"\002\022\021\n\texitValue\030\007 \001(\005*A\n\010FailType\022\014\n\010Thro" +
|
||||
"ttle\020\001\022\016\n\nDisconnect\020\002\022\t\n\005Abort\020\003\022\014\n\010Shu" +
|
||||
"tdown\020\004*,\n\tDirection\022\010\n\004Send\020\001\022\013\n\007Receiv" +
|
||||
"e\020\002\022\010\n\004Both\020\003B\035\n\031akka.remote.testconduct" +
|
||||
"orH\001"
|
||||
"\007address\030\002 \002(\0132\010.Address\"E\n\014EnterBarrier" +
|
||||
"\022\014\n\004name\030\001 \002(\t\022\026\n\002op\030\002 \002(\0162\n.BarrierOp\022\017" +
|
||||
"\n\007timeout\030\003 \001(\003\"6\n\016AddressRequest\022\014\n\004nod" +
|
||||
"e\030\001 \002(\t\022\026\n\004addr\030\002 \001(\0132\010.Address\"G\n\007Addre" +
|
||||
"ss\022\020\n\010protocol\030\001 \002(\t\022\016\n\006system\030\002 \002(\t\022\014\n\004",
|
||||
"host\030\003 \002(\t\022\014\n\004port\030\004 \002(\005\"\212\001\n\rInjectFailu" +
|
||||
"re\022\032\n\007failure\030\001 \002(\0162\t.FailType\022\035\n\tdirect" +
|
||||
"ion\030\002 \001(\0162\n.Direction\022\031\n\007address\030\003 \001(\0132\010" +
|
||||
".Address\022\020\n\010rateMBit\030\006 \001(\002\022\021\n\texitValue\030" +
|
||||
"\007 \001(\005*;\n\tBarrierOp\022\t\n\005Enter\020\001\022\010\n\004Fail\020\002\022" +
|
||||
"\r\n\tSucceeded\020\003\022\n\n\006Failed\020\004*A\n\010FailType\022\014" +
|
||||
"\n\010Throttle\020\001\022\016\n\nDisconnect\020\002\022\t\n\005Abort\020\003\022" +
|
||||
"\014\n\010Shutdown\020\004*,\n\tDirection\022\010\n\004Send\020\001\022\013\n\007" +
|
||||
"Receive\020\002\022\010\n\004Both\020\003B\035\n\031akka.remote.testc" +
|
||||
"onductorH\001"
|
||||
};
|
||||
com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner =
|
||||
new com.google.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() {
|
||||
|
|
@ -4096,7 +4247,7 @@ public final class TestConductorProtocol {
|
|||
internal_static_EnterBarrier_fieldAccessorTable = new
|
||||
com.google.protobuf.GeneratedMessage.FieldAccessorTable(
|
||||
internal_static_EnterBarrier_descriptor,
|
||||
new java.lang.String[] { "Name", "Status", },
|
||||
new java.lang.String[] { "Name", "Op", "Timeout", },
|
||||
akka.remote.testconductor.TestConductorProtocol.EnterBarrier.class,
|
||||
akka.remote.testconductor.TestConductorProtocol.EnterBarrier.Builder.class);
|
||||
internal_static_AddressRequest_descriptor =
|
||||
|
|
|
|||
|
|
@ -7,8 +7,10 @@ option optimize_for = SPEED;
|
|||
|
||||
/******************************************
|
||||
Compile with:
|
||||
cd ./akka-remote/src/main/protocol
|
||||
cd ./akka-remote-tests/src/main/protocol
|
||||
protoc TestConductorProtocol.proto --java_out ../java
|
||||
cd ../../../..
|
||||
./scripts/fix-protobuf.sh
|
||||
*******************************************/
|
||||
|
||||
message Wrapper {
|
||||
|
|
@ -24,9 +26,17 @@ message Hello {
|
|||
required Address address = 2;
|
||||
}
|
||||
|
||||
enum BarrierOp {
|
||||
Enter = 1;
|
||||
Fail = 2;
|
||||
Succeeded = 3;
|
||||
Failed = 4;
|
||||
}
|
||||
|
||||
message EnterBarrier {
|
||||
required string name = 1;
|
||||
optional bool status = 2;
|
||||
required BarrierOp op = 2;
|
||||
optional int64 timeout = 3;
|
||||
}
|
||||
|
||||
message AddressRequest {
|
||||
|
|
@ -47,11 +57,13 @@ enum FailType {
|
|||
Abort = 3;
|
||||
Shutdown = 4;
|
||||
}
|
||||
|
||||
enum Direction {
|
||||
Send = 1;
|
||||
Receive = 2;
|
||||
Both = 3;
|
||||
}
|
||||
|
||||
message InjectFailure {
|
||||
required FailType failure = 1;
|
||||
optional Direction direction = 2;
|
||||
|
|
|
|||
|
|
@ -8,8 +8,6 @@ import RemoteConnection.getAddrString
|
|||
import TestConductorProtocol._
|
||||
import org.jboss.netty.channel.{ Channel, SimpleChannelUpstreamHandler, ChannelHandlerContext, ChannelStateEvent, MessageEvent }
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import akka.util.Timeout
|
||||
import akka.util.Duration
|
||||
import akka.util.duration._
|
||||
import akka.pattern.ask
|
||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
|
|
@ -26,6 +24,7 @@ import akka.actor.OneForOneStrategy
|
|||
import akka.actor.SupervisorStrategy
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import akka.actor.Status
|
||||
import akka.util.{ Deadline, Timeout, Duration }
|
||||
|
||||
sealed trait Direction {
|
||||
def includes(other: Direction): Boolean
|
||||
|
|
@ -376,7 +375,8 @@ private[akka] class Controller(private var initialParticipants: Int, controllerP
|
|||
* BarrierTimeouts in the players).
|
||||
*/
|
||||
override def supervisorStrategy = OneForOneStrategy() {
|
||||
case BarrierTimeout(data) ⇒ SupervisorStrategy.Resume
|
||||
case BarrierTimeout(data) ⇒ failBarrier(data)
|
||||
case FailedBarrier(data) ⇒ failBarrier(data)
|
||||
case BarrierEmpty(data, msg) ⇒ SupervisorStrategy.Resume
|
||||
case WrongBarrier(name, client, data) ⇒ client ! ToClient(BarrierResult(name, false)); failBarrier(data)
|
||||
case ClientLost(data, node) ⇒ failBarrier(data)
|
||||
|
|
@ -426,6 +426,7 @@ private[akka] class Controller(private var initialParticipants: Int, controllerP
|
|||
case op: ServerOp ⇒
|
||||
op match {
|
||||
case _: EnterBarrier ⇒ barrier forward op
|
||||
case _: FailBarrier ⇒ barrier forward op
|
||||
case GetAddress(node) ⇒
|
||||
if (nodes contains node) sender ! ToClient(AddressReply(node, nodes(node).addr))
|
||||
else addrInterest += node -> ((addrInterest get node getOrElse Set()) + sender)
|
||||
|
|
@ -463,7 +464,7 @@ private[akka] object BarrierCoordinator {
|
|||
|
||||
case class RemoveClient(name: RoleName)
|
||||
|
||||
case class Data(clients: Set[Controller.NodeInfo], barrier: String, arrived: List[ActorRef])
|
||||
case class Data(clients: Set[Controller.NodeInfo], barrier: String, arrived: List[ActorRef], deadline: Deadline)
|
||||
|
||||
trait Printer { this: Product with Throwable with NoStackTrace ⇒
|
||||
override def toString = productPrefix + productIterator.mkString("(", ", ", ")")
|
||||
|
|
@ -471,6 +472,8 @@ private[akka] object BarrierCoordinator {
|
|||
|
||||
case class BarrierTimeout(data: Data)
|
||||
extends RuntimeException("timeout while waiting for barrier '" + data.barrier + "'") with NoStackTrace with Printer
|
||||
case class FailedBarrier(data: Data)
|
||||
extends RuntimeException("failing barrier '" + data.barrier + "'") with NoStackTrace with Printer
|
||||
case class DuplicateNode(data: Data, node: Controller.NodeInfo)
|
||||
extends RuntimeException(node.toString) with NoStackTrace with Printer
|
||||
case class WrongBarrier(barrier: String, client: ActorRef, data: Data)
|
||||
|
|
@ -497,21 +500,23 @@ private[akka] class BarrierCoordinator extends Actor with LoggingFSM[BarrierCoor
|
|||
import BarrierCoordinator._
|
||||
import akka.actor.FSM._
|
||||
import Controller._
|
||||
import akka.util.{ Timeout ⇒ auTimeout }
|
||||
|
||||
// this shall be set to false if all subsequent barriers shall fail
|
||||
// this shall be set to true if all subsequent barriers shall fail
|
||||
var failed = false
|
||||
|
||||
override def preRestart(reason: Throwable, message: Option[Any]) {}
|
||||
override def postRestart(reason: Throwable) { failed = true }
|
||||
|
||||
// TODO what happens with the other waiting players in case of a test failure?
|
||||
|
||||
startWith(Idle, Data(Set(), "", Nil))
|
||||
startWith(Idle, Data(Set(), "", Nil, null))
|
||||
|
||||
whenUnhandled {
|
||||
case Event(n: NodeInfo, d @ Data(clients, _, _)) ⇒
|
||||
case Event(n: NodeInfo, d @ Data(clients, _, _, _)) ⇒
|
||||
if (clients.find(_.name == n.name).isDefined) throw new DuplicateNode(d, n)
|
||||
stay using d.copy(clients = clients + n)
|
||||
case Event(ClientDisconnected(name), d @ Data(clients, _, arrived)) ⇒
|
||||
case Event(ClientDisconnected(name), d @ Data(clients, _, arrived, _)) ⇒
|
||||
if (clients.isEmpty) throw BarrierEmpty(d, "cannot disconnect " + name + ": no client to disconnect")
|
||||
(clients find (_.name == name)) match {
|
||||
case None ⇒ stay
|
||||
|
|
@ -520,38 +525,49 @@ private[akka] class BarrierCoordinator extends Actor with LoggingFSM[BarrierCoor
|
|||
}
|
||||
|
||||
when(Idle) {
|
||||
case Event(EnterBarrier(name), d @ Data(clients, _, _)) ⇒
|
||||
case Event(EnterBarrier(name, timeout), d @ Data(clients, _, _, _)) ⇒
|
||||
if (failed)
|
||||
stay replying ToClient(BarrierResult(name, false))
|
||||
else if (clients.map(_.fsm) == Set(sender))
|
||||
stay replying ToClient(BarrierResult(name, true))
|
||||
else if (clients.find(_.fsm == sender).isEmpty)
|
||||
stay replying ToClient(BarrierResult(name, false))
|
||||
else
|
||||
goto(Waiting) using d.copy(barrier = name, arrived = sender :: Nil)
|
||||
case Event(RemoveClient(name), d @ Data(clients, _, _)) ⇒
|
||||
else {
|
||||
goto(Waiting) using d.copy(barrier = name, arrived = sender :: Nil,
|
||||
deadline = getDeadline(timeout))
|
||||
}
|
||||
case Event(RemoveClient(name), d @ Data(clients, _, _, _)) ⇒
|
||||
if (clients.isEmpty) throw BarrierEmpty(d, "cannot remove " + name + ": no client to remove")
|
||||
stay using d.copy(clients = clients filterNot (_.name == name))
|
||||
}
|
||||
|
||||
onTransition {
|
||||
case Idle -> Waiting ⇒ setTimer("Timeout", StateTimeout, TestConductor().Settings.BarrierTimeout.duration, false)
|
||||
case Idle -> Waiting ⇒ setTimer("Timeout", StateTimeout, nextStateData.deadline.timeLeft, false)
|
||||
case Waiting -> Idle ⇒ cancelTimer("Timeout")
|
||||
}
|
||||
|
||||
when(Waiting) {
|
||||
case Event(EnterBarrier(name), d @ Data(clients, barrier, arrived)) ⇒
|
||||
case Event(EnterBarrier(name, timeout), d @ Data(clients, barrier, arrived, deadline)) ⇒
|
||||
if (name != barrier) throw WrongBarrier(name, sender, d)
|
||||
val together = if (clients.exists(_.fsm == sender)) sender :: arrived else arrived
|
||||
val enterDeadline = getDeadline(timeout)
|
||||
// we only allow the deadlines to get shorter
|
||||
if (enterDeadline < deadline) {
|
||||
setTimer("Timeout", StateTimeout, enterDeadline.timeLeft, false)
|
||||
handleBarrier(d.copy(arrived = together, deadline = enterDeadline))
|
||||
} else
|
||||
handleBarrier(d.copy(arrived = together))
|
||||
case Event(RemoveClient(name), d @ Data(clients, barrier, arrived)) ⇒
|
||||
case Event(RemoveClient(name), d @ Data(clients, barrier, arrived, _)) ⇒
|
||||
clients find (_.name == name) match {
|
||||
case None ⇒ stay
|
||||
case Some(client) ⇒
|
||||
handleBarrier(d.copy(clients = clients - client, arrived = arrived filterNot (_ == client.fsm)))
|
||||
}
|
||||
case Event(StateTimeout, data) ⇒
|
||||
throw BarrierTimeout(data)
|
||||
case Event(FailBarrier(name), d @ Data(_, barrier, _, _)) ⇒
|
||||
if (name != barrier) throw WrongBarrier(name, sender, d)
|
||||
throw FailedBarrier(d)
|
||||
case Event(StateTimeout, d) ⇒
|
||||
throw BarrierTimeout(d)
|
||||
}
|
||||
|
||||
initialize
|
||||
|
|
@ -568,5 +584,9 @@ private[akka] class BarrierCoordinator extends Actor with LoggingFSM[BarrierCoor
|
|||
}
|
||||
}
|
||||
|
||||
def getDeadline(timeout: Option[Duration]): Deadline = {
|
||||
Deadline.now + timeout.getOrElse(TestConductor().Settings.BarrierTimeout.duration)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,8 @@ import akka.remote.testconductor.{ TestConductorProtocol ⇒ TCP }
|
|||
import com.google.protobuf.Message
|
||||
import akka.actor.Address
|
||||
import org.jboss.netty.handler.codec.oneone.OneToOneDecoder
|
||||
import akka.util.Duration
|
||||
import akka.remote.testconductor.TestConductorProtocol.BarrierOp
|
||||
|
||||
case class RoleName(name: String)
|
||||
|
||||
|
|
@ -28,7 +30,8 @@ private[akka] sealed trait ConfirmedClientOp extends ClientOp
|
|||
*/
|
||||
private[akka] case class Hello(name: String, addr: Address) extends NetworkOp
|
||||
|
||||
private[akka] case class EnterBarrier(name: String) extends ServerOp with NetworkOp
|
||||
private[akka] case class EnterBarrier(name: String, timeout: Option[Duration]) extends ServerOp with NetworkOp
|
||||
private[akka] case class FailBarrier(name: String) extends ServerOp with NetworkOp
|
||||
private[akka] case class BarrierResult(name: String, success: Boolean) extends UnconfirmedClientOp with NetworkOp
|
||||
|
||||
private[akka] case class Throttle(node: RoleName, target: RoleName, direction: Direction, rateMBit: Float) extends CommandOp
|
||||
|
|
@ -72,10 +75,16 @@ private[akka] class MsgEncoder extends OneToOneEncoder {
|
|||
x match {
|
||||
case Hello(name, addr) ⇒
|
||||
w.setHello(TCP.Hello.newBuilder.setName(name).setAddress(addr))
|
||||
case EnterBarrier(name) ⇒
|
||||
w.setBarrier(TCP.EnterBarrier.newBuilder.setName(name))
|
||||
case EnterBarrier(name, timeout) ⇒
|
||||
val barrier = TCP.EnterBarrier.newBuilder.setName(name)
|
||||
timeout foreach (t ⇒ barrier.setTimeout(t.toNanos))
|
||||
barrier.setOp(BarrierOp.Enter)
|
||||
w.setBarrier(barrier)
|
||||
case BarrierResult(name, success) ⇒
|
||||
w.setBarrier(TCP.EnterBarrier.newBuilder.setName(name).setStatus(success))
|
||||
val res = if (success) BarrierOp.Succeeded else BarrierOp.Failed
|
||||
w.setBarrier(TCP.EnterBarrier.newBuilder.setName(name).setOp(res))
|
||||
case FailBarrier(name) ⇒
|
||||
w.setBarrier(TCP.EnterBarrier.newBuilder.setName(name).setOp(BarrierOp.Fail))
|
||||
case ThrottleMsg(target, dir, rate) ⇒
|
||||
w.setFailure(TCP.InjectFailure.newBuilder.setAddress(target)
|
||||
.setFailure(TCP.FailType.Throttle).setDirection(dir).setRateMBit(rate))
|
||||
|
|
@ -114,8 +123,13 @@ private[akka] class MsgDecoder extends OneToOneDecoder {
|
|||
Hello(h.getName, h.getAddress)
|
||||
} else if (w.hasBarrier) {
|
||||
val barrier = w.getBarrier
|
||||
if (barrier.hasStatus) BarrierResult(barrier.getName, barrier.getStatus)
|
||||
else EnterBarrier(w.getBarrier.getName)
|
||||
barrier.getOp match {
|
||||
case BarrierOp.Succeeded ⇒ BarrierResult(barrier.getName, true)
|
||||
case BarrierOp.Failed ⇒ BarrierResult(barrier.getName, false)
|
||||
case BarrierOp.Fail ⇒ FailBarrier(barrier.getName)
|
||||
case BarrierOp.Enter ⇒ EnterBarrier(barrier.getName,
|
||||
if (barrier.hasTimeout) Option(Duration.fromNanos(barrier.getTimeout)) else None)
|
||||
}
|
||||
} else if (w.hasFailure) {
|
||||
val f = w.getFailure
|
||||
import TCP.{ FailType ⇒ FT }
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import com.typesafe.config.ConfigFactory
|
|||
import akka.util.Timeout
|
||||
import akka.util.Duration
|
||||
import java.util.concurrent.TimeUnit.MILLISECONDS
|
||||
import akka.pattern.{ ask, pipe }
|
||||
import akka.pattern.{ ask, pipe, AskTimeoutException }
|
||||
import akka.dispatch.Await
|
||||
import scala.util.control.NoStackTrace
|
||||
import akka.actor.Status
|
||||
|
|
@ -26,6 +26,7 @@ import org.jboss.netty.channel.WriteCompletionEvent
|
|||
import java.net.ConnectException
|
||||
import akka.util.Deadline
|
||||
import akka.actor.Scheduler
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
/**
|
||||
* The Player is the client component of the
|
||||
|
|
@ -76,10 +77,31 @@ trait Player { this: TestConductorExt ⇒
|
|||
* throw an exception in case of timeouts or other errors.
|
||||
*/
|
||||
def enter(name: String*) {
|
||||
enter(Settings.BarrierTimeout, name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter the named barriers, one after the other, in the order given. Will
|
||||
* throw an exception in case of timeouts or other errors.
|
||||
*/
|
||||
def enter(timeout: Timeout, name: Seq[String]) {
|
||||
system.log.debug("entering barriers " + name.mkString("(", ", ", ")"))
|
||||
val stop = Deadline.now + timeout.duration
|
||||
name foreach { b ⇒
|
||||
import Settings.BarrierTimeout
|
||||
Await.result(client ? ToServer(EnterBarrier(b)), Duration.Inf)
|
||||
val barrierTimeout = stop.timeLeft
|
||||
if (barrierTimeout < Duration.Zero) {
|
||||
client ! ToServer(FailBarrier(b))
|
||||
throw new TimeoutException("Server timed out while waiting for barrier " + b);
|
||||
}
|
||||
try {
|
||||
implicit val timeout = Timeout(barrierTimeout + Settings.QueryTimeout.duration)
|
||||
Await.result(client ? ToServer(EnterBarrier(b, Option(barrierTimeout))), Duration.Inf)
|
||||
} catch {
|
||||
case e: AskTimeoutException ⇒
|
||||
client ! ToServer(FailBarrier(b))
|
||||
// Why don't TimeoutException have a constructor that takes a cause?
|
||||
throw new TimeoutException("Client timed out while waiting for barrier " + b);
|
||||
}
|
||||
system.log.debug("passed barrier {}", b)
|
||||
}
|
||||
}
|
||||
|
|
@ -88,7 +110,7 @@ trait Player { this: TestConductorExt ⇒
|
|||
* Query remote transport address of named node.
|
||||
*/
|
||||
def getAddressFor(name: RoleName): Future[Address] = {
|
||||
import Settings.BarrierTimeout
|
||||
import Settings.QueryTimeout
|
||||
client ? ToServer(GetAddress(name)) mapTo
|
||||
}
|
||||
}
|
||||
|
|
@ -168,7 +190,7 @@ private[akka] class ClientFSM(name: RoleName, controllerAddr: InetSocketAddress)
|
|||
case Event(ToServer(msg), d @ Data(Some(channel), None)) ⇒
|
||||
channel.write(msg)
|
||||
val token = msg match {
|
||||
case EnterBarrier(barrier) ⇒ barrier
|
||||
case EnterBarrier(barrier, timeout) ⇒ barrier
|
||||
case GetAddress(node) ⇒ node.name
|
||||
}
|
||||
stay using d.copy(runningOp = Some(token, sender))
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class LookupRemoteActorSpec extends MultiNodeSpec(LookupRemoteActorMultiJvmSpec)
|
|||
val masterAddress = testConductor.getAddressFor(master).await
|
||||
(hello ? "identify").await.asInstanceOf[ActorRef].path.address must equal(masterAddress)
|
||||
}
|
||||
testConductor.enter("done")
|
||||
enterBarrier("done")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ class NewRemoteActorSpec extends MultiNodeSpec(NewRemoteActorMultiJvmSpec)
|
|||
system.stop(actor)
|
||||
}
|
||||
|
||||
testConductor.enter("done")
|
||||
enterBarrier("done")
|
||||
}
|
||||
|
||||
"be locally instantiated on a remote node and be able to communicate through its RemoteActorRef (with deployOnAll)" taggedAs LongRunningTest in {
|
||||
|
|
@ -74,7 +74,7 @@ class NewRemoteActorSpec extends MultiNodeSpec(NewRemoteActorMultiJvmSpec)
|
|||
system.stop(actor)
|
||||
}
|
||||
|
||||
testConductor.enter("done")
|
||||
enterBarrier("done")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ class RandomRoutedRemoteActorSpec extends MultiNodeSpec(RandomRoutedRemoteActorM
|
|||
"be locally instantiated on a remote node and be able to communicate through its RemoteActorRef" taggedAs LongRunningTest in {
|
||||
|
||||
runOn(first, second, third) {
|
||||
testConductor.enter("start", "broadcast-end", "end", "done")
|
||||
enterBarrier("start", "broadcast-end", "end", "done")
|
||||
}
|
||||
|
||||
runOn(fourth) {
|
||||
testConductor.enter("start")
|
||||
enterBarrier("start")
|
||||
val actor = system.actorOf(Props[SomeActor].withRouter(RandomRouter()), "service-hello")
|
||||
actor.isInstanceOf[RoutedActorRef] must be(true)
|
||||
|
||||
|
|
@ -76,17 +76,17 @@ class RandomRoutedRemoteActorSpec extends MultiNodeSpec(RandomRoutedRemoteActorM
|
|||
case (replyMap, address) ⇒ replyMap + (address -> (replyMap(address) + 1))
|
||||
}
|
||||
|
||||
testConductor.enter("broadcast-end")
|
||||
enterBarrier("broadcast-end")
|
||||
actor ! Broadcast(PoisonPill)
|
||||
|
||||
testConductor.enter("end")
|
||||
enterBarrier("end")
|
||||
replies.values foreach { _ must be > (0) }
|
||||
replies.get(node(fourth).address) must be(None)
|
||||
|
||||
// shut down the actor before we let the other node(s) shut down so we don't try to send
|
||||
// "Terminate" to a shut down node
|
||||
system.stop(actor)
|
||||
testConductor.enter("done")
|
||||
enterBarrier("done")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ class RoundRobinRoutedRemoteActorSpec extends MultiNodeSpec(RoundRobinRoutedRemo
|
|||
"be locally instantiated on a remote node and be able to communicate through its RemoteActorRef" taggedAs LongRunningTest in {
|
||||
|
||||
runOn(first, second, third) {
|
||||
testConductor.enter("start", "broadcast-end", "end", "done")
|
||||
enterBarrier("start", "broadcast-end", "end", "done")
|
||||
}
|
||||
|
||||
runOn(fourth) {
|
||||
testConductor.enter("start")
|
||||
enterBarrier("start")
|
||||
val actor = system.actorOf(Props[SomeActor].withRouter(RoundRobinRouter()), "service-hello")
|
||||
actor.isInstanceOf[RoutedActorRef] must be(true)
|
||||
|
||||
|
|
@ -76,17 +76,17 @@ class RoundRobinRoutedRemoteActorSpec extends MultiNodeSpec(RoundRobinRoutedRemo
|
|||
case (replyMap, address) ⇒ replyMap + (address -> (replyMap(address) + 1))
|
||||
}
|
||||
|
||||
testConductor.enter("broadcast-end")
|
||||
enterBarrier("broadcast-end")
|
||||
actor ! Broadcast(PoisonPill)
|
||||
|
||||
testConductor.enter("end")
|
||||
enterBarrier("end")
|
||||
replies.values foreach { _ must be(iterationCount) }
|
||||
replies.get(node(fourth).address) must be(None)
|
||||
|
||||
// shut down the actor before we let the other node(s) shut down so we don't try to send
|
||||
// "Terminate" to a shut down node
|
||||
system.stop(actor)
|
||||
testConductor.enter("done")
|
||||
enterBarrier("done")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,11 +55,11 @@ class ScatterGatherRoutedRemoteActorSpec extends MultiNodeSpec(ScatterGatherRout
|
|||
"be locally instantiated on a remote node and be able to communicate through its RemoteActorRef" taggedAs LongRunningTest in {
|
||||
|
||||
runOn(first, second, third) {
|
||||
testConductor.enter("start", "broadcast-end", "end", "done")
|
||||
enterBarrier("start", "broadcast-end", "end", "done")
|
||||
}
|
||||
|
||||
runOn(fourth) {
|
||||
testConductor.enter("start")
|
||||
enterBarrier("start")
|
||||
val actor = system.actorOf(Props[SomeActor].withRouter(ScatterGatherFirstCompletedRouter(within = 10 seconds)), "service-hello")
|
||||
actor.isInstanceOf[RoutedActorRef] must be(true)
|
||||
|
||||
|
|
@ -76,17 +76,17 @@ class ScatterGatherRoutedRemoteActorSpec extends MultiNodeSpec(ScatterGatherRout
|
|||
case (replyMap, address) ⇒ replyMap + (address -> (replyMap(address) + 1))
|
||||
}
|
||||
|
||||
testConductor.enter("broadcast-end")
|
||||
enterBarrier("broadcast-end")
|
||||
actor ! Broadcast(PoisonPill)
|
||||
|
||||
testConductor.enter("end")
|
||||
enterBarrier("end")
|
||||
replies.values.sum must be === connectionCount * iterationCount
|
||||
replies.get(node(fourth).address) must be(None)
|
||||
|
||||
// shut down the actor before we let the other node(s) shut down so we don't try to send
|
||||
// "Terminate" to a shut down node
|
||||
system.stop(actor)
|
||||
testConductor.enter("done")
|
||||
enterBarrier("done")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ class TestConductorSpec extends MultiNodeSpec(TestConductorMultiJvmSpec) with Im
|
|||
}), "echo")
|
||||
}
|
||||
|
||||
testConductor.enter("name")
|
||||
enterBarrier("name")
|
||||
}
|
||||
|
||||
"support throttling of network connections" taggedAs LongRunningTest in {
|
||||
|
|
@ -62,7 +62,7 @@ class TestConductorSpec extends MultiNodeSpec(TestConductorMultiJvmSpec) with Im
|
|||
testConductor.throttle(slave, master, Direction.Send, rateMBit = 0.01).await
|
||||
}
|
||||
|
||||
testConductor.enter("throttled_send")
|
||||
enterBarrier("throttled_send")
|
||||
|
||||
runOn(slave) {
|
||||
for (i ← 0 to 9) echo ! i
|
||||
|
|
@ -73,14 +73,14 @@ class TestConductorSpec extends MultiNodeSpec(TestConductorMultiJvmSpec) with Im
|
|||
receiveN(9) must be(1 to 9)
|
||||
}
|
||||
|
||||
testConductor.enter("throttled_send2")
|
||||
enterBarrier("throttled_send2")
|
||||
|
||||
runOn(master) {
|
||||
testConductor.throttle(slave, master, Direction.Send, -1).await
|
||||
testConductor.throttle(slave, master, Direction.Receive, rateMBit = 0.01).await
|
||||
}
|
||||
|
||||
testConductor.enter("throttled_recv")
|
||||
enterBarrier("throttled_recv")
|
||||
|
||||
runOn(slave) {
|
||||
for (i ← 10 to 19) echo ! i
|
||||
|
|
@ -98,7 +98,7 @@ class TestConductorSpec extends MultiNodeSpec(TestConductorMultiJvmSpec) with Im
|
|||
receiveN(9) must be(11 to 19)
|
||||
}
|
||||
|
||||
testConductor.enter("throttled_recv2")
|
||||
enterBarrier("throttled_recv2")
|
||||
|
||||
runOn(master) {
|
||||
testConductor.throttle(slave, master, Direction.Receive, -1).await
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import org.scalatest.BeforeAndAfterEach
|
|||
import java.net.InetSocketAddress
|
||||
import java.net.InetAddress
|
||||
import akka.testkit.TimingTest
|
||||
import akka.util.{ Timeout, Duration }
|
||||
|
||||
object BarrierSpec {
|
||||
case class Failed(ref: ActorRef, thr: Throwable)
|
||||
|
|
@ -31,7 +32,7 @@ object BarrierSpec {
|
|||
"""
|
||||
}
|
||||
|
||||
class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with BeforeAndAfterEach {
|
||||
class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender {
|
||||
|
||||
import BarrierSpec._
|
||||
import Controller._
|
||||
|
|
@ -41,10 +42,6 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
val B = RoleName("b")
|
||||
val C = RoleName("c")
|
||||
|
||||
override def afterEach {
|
||||
system.eventStream.setLogLevel(Logging.WarningLevel)
|
||||
}
|
||||
|
||||
"A BarrierCoordinator" must {
|
||||
|
||||
"register clients and remove them" taggedAs TimingTest in {
|
||||
|
|
@ -55,7 +52,7 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
EventFilter[BarrierEmpty](occurrences = 1) intercept {
|
||||
b ! RemoveClient(A)
|
||||
}
|
||||
expectMsg(Failed(b, BarrierEmpty(Data(Set(), "", Nil), "cannot remove RoleName(a): no client to remove")))
|
||||
expectMsg(Failed(b, BarrierEmpty(Data(Set(), "", Nil, null), "cannot remove RoleName(a): no client to remove")))
|
||||
}
|
||||
|
||||
"register clients and disconnect them" taggedAs TimingTest in {
|
||||
|
|
@ -65,17 +62,17 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
EventFilter[ClientLost](occurrences = 1) intercept {
|
||||
b ! ClientDisconnected(A)
|
||||
}
|
||||
expectMsg(Failed(b, ClientLost(Data(Set(), "", Nil), A)))
|
||||
expectMsg(Failed(b, ClientLost(Data(Set(), "", Nil, null), A)))
|
||||
EventFilter[BarrierEmpty](occurrences = 1) intercept {
|
||||
b ! ClientDisconnected(A)
|
||||
}
|
||||
expectMsg(Failed(b, BarrierEmpty(Data(Set(), "", Nil), "cannot disconnect RoleName(a): no client to disconnect")))
|
||||
expectMsg(Failed(b, BarrierEmpty(Data(Set(), "", Nil, null), "cannot disconnect RoleName(a): no client to disconnect")))
|
||||
}
|
||||
|
||||
"fail entering barrier when nobody registered" taggedAs TimingTest in {
|
||||
val b = getBarrier()
|
||||
b ! EnterBarrier("b")
|
||||
expectMsg(ToClient(BarrierResult("b", false)))
|
||||
b ! EnterBarrier("bar1", None)
|
||||
expectMsg(ToClient(BarrierResult("bar1", false)))
|
||||
}
|
||||
|
||||
"enter barrier" taggedAs TimingTest in {
|
||||
|
|
@ -83,12 +80,12 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
val a, b = TestProbe()
|
||||
barrier ! NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar2", None))
|
||||
noMsg(a, b)
|
||||
within(2 second) {
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
a.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
within(2 seconds) {
|
||||
b.send(barrier, EnterBarrier("bar2", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar2", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar2", true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -97,15 +94,15 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
val a, b, c = TestProbe()
|
||||
barrier ! NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar3", None))
|
||||
barrier ! NodeInfo(C, AddressFromURIString("akka://sys"), c.ref)
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
b.send(barrier, EnterBarrier("bar3", None))
|
||||
noMsg(a, b, c)
|
||||
within(2 second) {
|
||||
c.send(barrier, EnterBarrier("bar"))
|
||||
a.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
c.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
within(2 seconds) {
|
||||
c.send(barrier, EnterBarrier("bar3", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar3", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar3", true)))
|
||||
c.expectMsg(ToClient(BarrierResult("bar3", true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -115,14 +112,14 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
barrier ! NodeInfo(C, AddressFromURIString("akka://sys"), c.ref)
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar4", None))
|
||||
b.send(barrier, EnterBarrier("bar4", None))
|
||||
barrier ! RemoveClient(A)
|
||||
barrier ! ClientDisconnected(A)
|
||||
noMsg(a, b, c)
|
||||
b.within(2 second) {
|
||||
b.within(2 seconds) {
|
||||
barrier ! RemoveClient(C)
|
||||
b.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar4", true)))
|
||||
}
|
||||
barrier ! ClientDisconnected(C)
|
||||
expectNoMsg(1 second)
|
||||
|
|
@ -133,9 +130,9 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
val a, b = TestProbe()
|
||||
barrier ! NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar5", None))
|
||||
barrier ! RemoveClient(A)
|
||||
b.send(barrier, EnterBarrier("foo"))
|
||||
b.send(barrier, EnterBarrier("foo", None))
|
||||
b.expectMsg(ToClient(BarrierResult("foo", true)))
|
||||
}
|
||||
|
||||
|
|
@ -145,11 +142,15 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
val nodeA = NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
barrier ! nodeA
|
||||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar6", None))
|
||||
EventFilter[ClientLost](occurrences = 1) intercept {
|
||||
barrier ! ClientDisconnected(B)
|
||||
}
|
||||
expectMsg(Failed(barrier, ClientLost(Data(Set(nodeA), "bar", a.ref :: Nil), B)))
|
||||
val msg = expectMsgType[Failed]
|
||||
msg match {
|
||||
case Failed(barrier, thr: ClientLost) if (thr == ClientLost(Data(Set(nodeA), "bar6", a.ref :: Nil, thr.data.deadline), B)) ⇒
|
||||
case x ⇒ fail("Expected " + Failed(barrier, ClientLost(Data(Set(nodeA), "bar6", a.ref :: Nil, null), B)) + " but got " + x)
|
||||
}
|
||||
}
|
||||
|
||||
"fail barrier with disconnecing node who already arrived" taggedAs TimingTest in {
|
||||
|
|
@ -160,12 +161,16 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! nodeA
|
||||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
barrier ! nodeC
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar7", None))
|
||||
b.send(barrier, EnterBarrier("bar7", None))
|
||||
EventFilter[ClientLost](occurrences = 1) intercept {
|
||||
barrier ! ClientDisconnected(B)
|
||||
}
|
||||
expectMsg(Failed(barrier, ClientLost(Data(Set(nodeA, nodeC), "bar", a.ref :: Nil), B)))
|
||||
val msg = expectMsgType[Failed]
|
||||
msg match {
|
||||
case Failed(barrier, thr: ClientLost) if (thr == ClientLost(Data(Set(nodeA, nodeC), "bar7", a.ref :: Nil, thr.data.deadline), B)) ⇒
|
||||
case x ⇒ fail("Expected " + Failed(barrier, ClientLost(Data(Set(nodeA, nodeC), "bar7", a.ref :: Nil, null), B)) + " but got " + x)
|
||||
}
|
||||
}
|
||||
|
||||
"fail when entering wrong barrier" taggedAs TimingTest in {
|
||||
|
|
@ -175,11 +180,15 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! nodeA
|
||||
val nodeB = NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
barrier ! nodeB
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar8", None))
|
||||
EventFilter[WrongBarrier](occurrences = 1) intercept {
|
||||
b.send(barrier, EnterBarrier("foo"))
|
||||
b.send(barrier, EnterBarrier("foo", None))
|
||||
}
|
||||
val msg = expectMsgType[Failed]
|
||||
msg match {
|
||||
case Failed(barrier, thr: WrongBarrier) if (thr == WrongBarrier("foo", b.ref, Data(Set(nodeA, nodeB), "bar8", a.ref :: Nil, thr.data.deadline))) ⇒
|
||||
case x ⇒ fail("Expected " + Failed(barrier, WrongBarrier("foo", b.ref, Data(Set(nodeA, nodeB), "bar8", a.ref :: Nil, null))) + " but got " + x)
|
||||
}
|
||||
expectMsg(Failed(barrier, WrongBarrier("foo", b.ref, Data(Set(nodeA, nodeB), "bar", a.ref :: Nil))))
|
||||
}
|
||||
|
||||
"fail barrier after first failure" taggedAs TimingTest in {
|
||||
|
|
@ -188,10 +197,14 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
EventFilter[BarrierEmpty](occurrences = 1) intercept {
|
||||
barrier ! RemoveClient(A)
|
||||
}
|
||||
expectMsg(Failed(barrier, BarrierEmpty(Data(Set(), "", Nil), "cannot remove RoleName(a): no client to remove")))
|
||||
val msg = expectMsgType[Failed]
|
||||
msg match {
|
||||
case Failed(barrier, thr: BarrierEmpty) if (thr == BarrierEmpty(Data(Set(), "", Nil, thr.data.deadline), "cannot remove RoleName(a): no client to remove")) ⇒
|
||||
case x ⇒ fail("Expected " + Failed(barrier, BarrierEmpty(Data(Set(), "", Nil, null), "cannot remove RoleName(a): no client to remove")) + " but got " + x)
|
||||
}
|
||||
barrier ! NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
a.send(barrier, EnterBarrier("right"))
|
||||
a.expectMsg(ToClient(BarrierResult("right", false)))
|
||||
a.send(barrier, EnterBarrier("bar9", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar9", false)))
|
||||
}
|
||||
|
||||
"fail after barrier timeout" taggedAs TimingTest in {
|
||||
|
|
@ -201,9 +214,13 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
val nodeB = NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
barrier ! nodeA
|
||||
barrier ! nodeB
|
||||
a.send(barrier, EnterBarrier("right"))
|
||||
a.send(barrier, EnterBarrier("bar10", None))
|
||||
EventFilter[BarrierTimeout](occurrences = 1) intercept {
|
||||
expectMsg(7 seconds, Failed(barrier, BarrierTimeout(Data(Set(nodeA, nodeB), "right", a.ref :: Nil))))
|
||||
val msg = expectMsgType[Failed](7 seconds)
|
||||
msg match {
|
||||
case Failed(barrier, thr: BarrierTimeout) if (thr == BarrierTimeout(Data(Set(nodeA, nodeB), "bar10", a.ref :: Nil, thr.data.deadline))) ⇒
|
||||
case x ⇒ fail("Expected " + Failed(barrier, BarrierTimeout(Data(Set(nodeA, nodeB), "bar10", a.ref :: Nil, null))) + " but got " + x)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -216,7 +233,11 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
EventFilter[DuplicateNode](occurrences = 1) intercept {
|
||||
barrier ! nodeB
|
||||
}
|
||||
expectMsg(Failed(barrier, DuplicateNode(Data(Set(nodeA), "", Nil), nodeB)))
|
||||
val msg = expectMsgType[Failed]
|
||||
msg match {
|
||||
case Failed(barrier, thr: DuplicateNode) if (thr == DuplicateNode(Data(Set(nodeA), "", Nil, thr.data.deadline), nodeB)) ⇒
|
||||
case x ⇒ fail("Expected " + Failed(barrier, DuplicateNode(Data(Set(nodeA), "", Nil, null), nodeB)) + " but got " + x)
|
||||
}
|
||||
}
|
||||
|
||||
"finally have no failure messages left" taggedAs TimingTest in {
|
||||
|
|
@ -253,7 +274,7 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
|
||||
"fail entering barrier when nobody registered" taggedAs TimingTest in {
|
||||
val b = getController(0)
|
||||
b ! EnterBarrier("b")
|
||||
b ! EnterBarrier("b", None)
|
||||
expectMsg(ToClient(BarrierResult("b", false)))
|
||||
}
|
||||
|
||||
|
|
@ -264,12 +285,12 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar11", None))
|
||||
noMsg(a, b)
|
||||
within(2 second) {
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
a.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
within(2 seconds) {
|
||||
b.send(barrier, EnterBarrier("bar11", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar11", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar11", true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -280,16 +301,16 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar12", None))
|
||||
barrier ! NodeInfo(C, AddressFromURIString("akka://sys"), c.ref)
|
||||
c.expectMsg(ToClient(Done))
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
b.send(barrier, EnterBarrier("bar12", None))
|
||||
noMsg(a, b, c)
|
||||
within(2 second) {
|
||||
c.send(barrier, EnterBarrier("bar"))
|
||||
a.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
c.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
within(2 seconds) {
|
||||
c.send(barrier, EnterBarrier("bar12", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar12", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar12", true)))
|
||||
c.expectMsg(ToClient(BarrierResult("bar12", true)))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -302,14 +323,14 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
c.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar13", None))
|
||||
b.send(barrier, EnterBarrier("bar13", None))
|
||||
barrier ! Remove(A)
|
||||
barrier ! ClientDisconnected(A)
|
||||
noMsg(a, b, c)
|
||||
b.within(2 second) {
|
||||
b.within(2 seconds) {
|
||||
barrier ! Remove(C)
|
||||
b.expectMsg(ToClient(BarrierResult("bar", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar13", true)))
|
||||
}
|
||||
barrier ! ClientDisconnected(C)
|
||||
expectNoMsg(1 second)
|
||||
|
|
@ -322,9 +343,9 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar14", None))
|
||||
barrier ! Remove(A)
|
||||
b.send(barrier, EnterBarrier("foo"))
|
||||
b.send(barrier, EnterBarrier("foo", None))
|
||||
b.expectMsg(ToClient(BarrierResult("foo", true)))
|
||||
}
|
||||
|
||||
|
|
@ -336,13 +357,13 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar15", None))
|
||||
barrier ! ClientDisconnected(RoleName("unknown"))
|
||||
noMsg(a)
|
||||
EventFilter[ClientLost](occurrences = 1) intercept {
|
||||
barrier ! ClientDisconnected(B)
|
||||
}
|
||||
a.expectMsg(ToClient(BarrierResult("bar", false)))
|
||||
a.expectMsg(ToClient(BarrierResult("bar15", false)))
|
||||
}
|
||||
|
||||
"fail barrier with disconnecing node who already arrived" taggedAs TimingTest in {
|
||||
|
|
@ -356,12 +377,12 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
c.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
b.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar16", None))
|
||||
b.send(barrier, EnterBarrier("bar16", None))
|
||||
EventFilter[ClientLost](occurrences = 1) intercept {
|
||||
barrier ! ClientDisconnected(B)
|
||||
}
|
||||
a.expectMsg(ToClient(BarrierResult("bar", false)))
|
||||
a.expectMsg(ToClient(BarrierResult("bar16", false)))
|
||||
}
|
||||
|
||||
"fail when entering wrong barrier" taggedAs TimingTest in {
|
||||
|
|
@ -373,15 +394,15 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! nodeB
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar"))
|
||||
a.send(barrier, EnterBarrier("bar17", None))
|
||||
EventFilter[WrongBarrier](occurrences = 1) intercept {
|
||||
b.send(barrier, EnterBarrier("foo"))
|
||||
b.send(barrier, EnterBarrier("foo", None))
|
||||
}
|
||||
a.expectMsg(ToClient(BarrierResult("bar", false)))
|
||||
a.expectMsg(ToClient(BarrierResult("bar17", false)))
|
||||
b.expectMsg(ToClient(BarrierResult("foo", false)))
|
||||
}
|
||||
|
||||
"not really fail after barrier timeout" taggedAs TimingTest in {
|
||||
"fail after barrier timeout" taggedAs TimingTest in {
|
||||
val barrier = getController(2)
|
||||
val a, b = TestProbe()
|
||||
val nodeA = NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
|
|
@ -390,13 +411,13 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
barrier ! nodeB
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("right"))
|
||||
a.send(barrier, EnterBarrier("bar18", Option(2 seconds)))
|
||||
EventFilter[BarrierTimeout](occurrences = 1) intercept {
|
||||
Thread.sleep(5000)
|
||||
Thread.sleep(4000)
|
||||
}
|
||||
b.send(barrier, EnterBarrier("right"))
|
||||
a.expectMsg(ToClient(BarrierResult("right", true)))
|
||||
b.expectMsg(ToClient(BarrierResult("right", true)))
|
||||
b.send(barrier, EnterBarrier("bar18", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar18", false)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar18", false)))
|
||||
}
|
||||
|
||||
"fail if a node registers twice" taggedAs TimingTest in {
|
||||
|
|
@ -423,8 +444,75 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
controller ! nodeB
|
||||
b.expectMsg(ToClient(BarrierResult("initial startup", false)))
|
||||
}
|
||||
a.send(controller, EnterBarrier("x"))
|
||||
a.expectMsg(ToClient(BarrierResult("x", false)))
|
||||
a.send(controller, EnterBarrier("bar19", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar19", false)))
|
||||
}
|
||||
|
||||
"fail subsequent barriers after foreced failure" taggedAs TimingTest in {
|
||||
val barrier = getController(2)
|
||||
val a, b = TestProbe()
|
||||
val nodeA = NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
val nodeB = NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
barrier ! nodeA
|
||||
barrier ! nodeB
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar20", Option(2 seconds)))
|
||||
EventFilter[FailedBarrier](occurrences = 1) intercept {
|
||||
b.send(barrier, FailBarrier("bar20"))
|
||||
a.expectMsg(ToClient(BarrierResult("bar20", false)))
|
||||
b.expectNoMsg(1 second)
|
||||
}
|
||||
a.send(barrier, EnterBarrier("bar21", None))
|
||||
b.send(barrier, EnterBarrier("bar21", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar21", false)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar21", false)))
|
||||
}
|
||||
|
||||
"timeout within the shortest timeout if the new timeout is shorter" taggedAs TimingTest in {
|
||||
val barrier = getController(3)
|
||||
val a, b, c = TestProbe()
|
||||
val nodeA = NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
val nodeB = NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
val nodeC = NodeInfo(C, AddressFromURIString("akka://sys"), c.ref)
|
||||
barrier ! nodeA
|
||||
barrier ! nodeB
|
||||
barrier ! nodeC
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
c.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar22", Option(10 seconds)))
|
||||
b.send(barrier, EnterBarrier("bar22", Option(2 seconds)))
|
||||
EventFilter[BarrierTimeout](occurrences = 1) intercept {
|
||||
Thread.sleep(4000)
|
||||
}
|
||||
c.send(barrier, EnterBarrier("bar22", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar22", false)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar22", false)))
|
||||
c.expectMsg(ToClient(BarrierResult("bar22", false)))
|
||||
}
|
||||
|
||||
"timeout within the shortest timeout if the new timeout is longer" taggedAs TimingTest in {
|
||||
val barrier = getController(3)
|
||||
val a, b, c = TestProbe()
|
||||
val nodeA = NodeInfo(A, AddressFromURIString("akka://sys"), a.ref)
|
||||
val nodeB = NodeInfo(B, AddressFromURIString("akka://sys"), b.ref)
|
||||
val nodeC = NodeInfo(C, AddressFromURIString("akka://sys"), c.ref)
|
||||
barrier ! nodeA
|
||||
barrier ! nodeB
|
||||
barrier ! nodeC
|
||||
a.expectMsg(ToClient(Done))
|
||||
b.expectMsg(ToClient(Done))
|
||||
c.expectMsg(ToClient(Done))
|
||||
a.send(barrier, EnterBarrier("bar23", Option(2 seconds)))
|
||||
b.send(barrier, EnterBarrier("bar23", Option(10 seconds)))
|
||||
EventFilter[BarrierTimeout](occurrences = 1) intercept {
|
||||
Thread.sleep(4000)
|
||||
}
|
||||
c.send(barrier, EnterBarrier("bar23", None))
|
||||
a.expectMsg(ToClient(BarrierResult("bar23", false)))
|
||||
b.expectMsg(ToClient(BarrierResult("bar23", false)))
|
||||
c.expectMsg(ToClient(BarrierResult("bar23", false)))
|
||||
}
|
||||
|
||||
"finally have no failure messages left" taggedAs TimingTest in {
|
||||
|
|
@ -469,4 +557,7 @@ class BarrierSpec extends AkkaSpec(BarrierSpec.config) with ImplicitSender with
|
|||
probes foreach (_.msgAvailable must be(false))
|
||||
}
|
||||
|
||||
private def data(clients: Set[Controller.NodeInfo], barrier: String, arrived: List[ActorRef], previous: Data): Data = {
|
||||
Data(clients, barrier, arrived, previous.deadline)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ import akka.dispatch.Await
|
|||
import akka.dispatch.Await.Awaitable
|
||||
import akka.remote.testconductor.{ TestConductorExt, TestConductor, RoleName }
|
||||
import akka.testkit.AkkaSpec
|
||||
import akka.util.{ NonFatal, Duration }
|
||||
import akka.util.{ Timeout, NonFatal, Duration }
|
||||
|
||||
/**
|
||||
* Configure the role names and participants of the test, including configuration settings.
|
||||
|
|
@ -182,6 +182,14 @@ abstract class MultiNodeSpec(val myself: RoleName, _system: ActorSystem, _roles:
|
|||
if (nodes exists (_ == myself)) yes else no
|
||||
}
|
||||
|
||||
/**
|
||||
* Enter the named barriers in the order given. Use the remaining duration from
|
||||
* the innermost enclosing `within` block or the default `BarrierTimeout`
|
||||
*/
|
||||
def enterBarrier(name: String*) {
|
||||
testConductor.enter(Timeout.durationToTimeout(remainingOr(testConductor.Settings.BarrierTimeout.duration)), name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Query the controller for the transport address of the given node (by role name) and
|
||||
* return that as an ActorPath for easy composition:
|
||||
|
|
@ -193,11 +201,12 @@ abstract class MultiNodeSpec(val myself: RoleName, _system: ActorSystem, _roles:
|
|||
def node(role: RoleName): ActorPath = RootActorPath(testConductor.getAddressFor(role).await)
|
||||
|
||||
/**
|
||||
* Enrich `.await()` onto all Awaitables, using BarrierTimeout.
|
||||
* Enrich `.await()` onto all Awaitables, using remaining duration from the innermost
|
||||
* enclosing `within` block or QueryTimeout.
|
||||
*/
|
||||
implicit def awaitHelper[T](w: Awaitable[T]) = new AwaitHelper(w)
|
||||
class AwaitHelper[T](w: Awaitable[T]) {
|
||||
def await: T = Await.result(w, testConductor.Settings.BarrierTimeout.duration)
|
||||
def await: T = Await.result(w, remainingOr(testConductor.Settings.QueryTimeout.duration))
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
@ -206,9 +215,11 @@ abstract class MultiNodeSpec(val myself: RoleName, _system: ActorSystem, _roles:
|
|||
|
||||
private val controllerAddr = new InetSocketAddress(nodeNames(0), 4711)
|
||||
if (selfIndex == 0) {
|
||||
testConductor.startController(initialParticipants, myself, controllerAddr).await
|
||||
Await.result(testConductor.startController(initialParticipants, myself, controllerAddr),
|
||||
testConductor.Settings.BarrierTimeout.duration)
|
||||
} else {
|
||||
testConductor.startClient(myself, controllerAddr).await
|
||||
Await.result(testConductor.startClient(myself, controllerAddr),
|
||||
testConductor.Settings.BarrierTimeout.duration)
|
||||
}
|
||||
|
||||
// now add deployments, if so desired
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ option optimize_for = SPEED;
|
|||
Compile with:
|
||||
cd ./akka-remote/src/main/protocol
|
||||
protoc RemoteProtocol.proto --java_out ../java
|
||||
cd ../../../..
|
||||
./scripts/fix-protobuf.sh
|
||||
*******************************************/
|
||||
|
||||
message AkkaRemoteProtocol {
|
||||
|
|
|
|||
|
|
@ -190,9 +190,10 @@ akka {
|
|||
# 'TLSv1.1', 'TLSv1.2'
|
||||
protocol = "TLSv1"
|
||||
|
||||
# Examples: [ "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA" ]
|
||||
# You need to install the JCE Unlimited Strength Jurisdiction Policy Files to use AES 256
|
||||
# More info here: http://docs.oracle.com/javase/7/docs/technotes/guides/security/SunProviders.html#SunJCEProvider
|
||||
supported-algorithms = ["TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"]
|
||||
enabled-algorithms = ["TLS_RSA_WITH_AES_128_CBC_SHA"]
|
||||
|
||||
# Using /dev/./urandom is only necessary when using SHA1PRNG on Linux to prevent blocking
|
||||
# It is NOT as secure because it reuses the seed
|
||||
|
|
@ -207,6 +208,7 @@ akka {
|
|||
# The following use one of 3 possible seed sources, depending on availability: /dev/random, random.org and SecureRandom (provided by Java)
|
||||
# "AES128CounterRNGSecure"
|
||||
# "AES256CounterRNGSecure" (Install JCE Unlimited Strength Jurisdiction Policy Files first)
|
||||
# Setting a value here may require you to supply the appropriate cipher suite (see enabled-algorithms section above)
|
||||
random-number-generator = ""
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,13 +17,17 @@ import akka.security.provider.AkkaProvider
|
|||
* Internal use only
|
||||
*/
|
||||
private[akka] object NettySSLSupport {
|
||||
|
||||
val akka = new AkkaProvider
|
||||
Security.addProvider(akka)
|
||||
|
||||
/**
|
||||
* Construct a SSLHandler which can be inserted into a Netty server/client pipeline
|
||||
*/
|
||||
def apply(settings: NettySettings, log: LoggingAdapter, isClient: Boolean): SslHandler =
|
||||
if (isClient) initialiseClientSSL(settings, log) else initialiseServerSSL(settings, log)
|
||||
if (isClient) initializeClientSSL(settings, log) else initializeServerSSL(settings, log)
|
||||
|
||||
def initialiseCustomSecureRandom(rngName: Option[String], sourceOfRandomness: Option[String], log: LoggingAdapter): SecureRandom = {
|
||||
def initializeCustomSecureRandom(rngName: Option[String], sourceOfRandomness: Option[String], log: LoggingAdapter): SecureRandom = {
|
||||
/**
|
||||
* According to this bug report: http://bugs.sun.com/view_bug.do?bug_id=6202721
|
||||
* Using /dev/./urandom is only necessary when using SHA1PRNG on Linux
|
||||
|
|
@ -34,8 +38,6 @@ private[akka] object NettySSLSupport {
|
|||
val rng = rngName match {
|
||||
case Some(r @ ("AES128CounterRNGFast" | "AES128CounterRNGSecure" | "AES256CounterRNGSecure")) ⇒
|
||||
log.debug("SSL random number generator set to: {}", r)
|
||||
val akka = new AkkaProvider
|
||||
Security.addProvider(akka)
|
||||
SecureRandom.getInstance(r, akka)
|
||||
case Some("SHA1PRNG") ⇒
|
||||
log.debug("SSL random number generator set to: SHA1PRNG")
|
||||
|
|
@ -53,8 +55,23 @@ private[akka] object NettySSLSupport {
|
|||
rng
|
||||
}
|
||||
|
||||
private def initialiseClientSSL(settings: NettySettings, log: LoggingAdapter): SslHandler = {
|
||||
def initializeClientSSL(settings: NettySettings, log: LoggingAdapter): SslHandler = {
|
||||
log.debug("Client SSL is enabled, initialising ...")
|
||||
|
||||
def constructClientContext(settings: NettySettings, log: LoggingAdapter, trustStorePath: String, trustStorePassword: String, protocol: String): Option[SSLContext] =
|
||||
try {
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
|
||||
val trustStore = KeyStore.getInstance(KeyStore.getDefaultType)
|
||||
trustStore.load(new FileInputStream(trustStorePath), trustStorePassword.toCharArray) //FIXME does the FileInputStream need to be closed?
|
||||
trustManagerFactory.init(trustStore)
|
||||
val trustManagers: Array[TrustManager] = trustManagerFactory.getTrustManagers
|
||||
Option(SSLContext.getInstance(protocol)) map { ctx ⇒ ctx.init(null, trustManagers, initializeCustomSecureRandom(settings.SSLRandomNumberGenerator, settings.SSLRandomSource, log)); ctx }
|
||||
} catch {
|
||||
case e: FileNotFoundException ⇒ throw new RemoteTransportException("Client SSL connection could not be established because trust store could not be loaded", e)
|
||||
case e: IOException ⇒ throw new RemoteTransportException("Client SSL connection could not be established because: " + e.getMessage, e)
|
||||
case e: GeneralSecurityException ⇒ throw new RemoteTransportException("Client SSL connection could not be established because SSL context could not be constructed", e)
|
||||
}
|
||||
|
||||
((settings.SSLTrustStore, settings.SSLTrustStorePassword, settings.SSLProtocol) match {
|
||||
case (Some(trustStore), Some(password), Some(protocol)) ⇒ constructClientContext(settings, log, trustStore, password, protocol)
|
||||
case (trustStore, password, protocol) ⇒ throw new GeneralSecurityException(
|
||||
|
|
@ -67,11 +84,11 @@ private[akka] object NettySSLSupport {
|
|||
log.debug("Using client SSL context to create SSLEngine ...")
|
||||
val sslEngine = context.createSSLEngine
|
||||
sslEngine.setUseClientMode(true)
|
||||
sslEngine.setEnabledCipherSuites(settings.SSLSupportedAlgorithms.toArray.map(_.toString))
|
||||
sslEngine.setEnabledCipherSuites(settings.SSLEnabledAlgorithms.toArray.map(_.toString))
|
||||
new SslHandler(sslEngine)
|
||||
case None ⇒
|
||||
throw new GeneralSecurityException(
|
||||
"""Failed to initialise client SSL because SSL context could not be found." +
|
||||
"""Failed to initialize client SSL because SSL context could not be found." +
|
||||
"Make sure your settings are correct: [trust-store: %s] [trust-store-password: %s] [protocol: %s]""".format(
|
||||
settings.SSLTrustStore,
|
||||
settings.SSLTrustStorePassword,
|
||||
|
|
@ -79,24 +96,22 @@ private[akka] object NettySSLSupport {
|
|||
}
|
||||
}
|
||||
|
||||
private def constructClientContext(settings: NettySettings, log: LoggingAdapter, trustStorePath: String, trustStorePassword: String, protocol: String): Option[SSLContext] = {
|
||||
try {
|
||||
val trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
|
||||
val trustStore = KeyStore.getInstance(KeyStore.getDefaultType)
|
||||
trustStore.load(new FileInputStream(trustStorePath), trustStorePassword.toCharArray) //FIXME does the FileInputStream need to be closed?
|
||||
trustManagerFactory.init(trustStore)
|
||||
val trustManagers: Array[TrustManager] = trustManagerFactory.getTrustManagers
|
||||
Option(SSLContext.getInstance(protocol)) map { ctx ⇒ ctx.init(null, trustManagers, initialiseCustomSecureRandom(settings.SSLRandomNumberGenerator, settings.SSLRandomSource, log)); ctx }
|
||||
} catch {
|
||||
case e: FileNotFoundException ⇒ throw new RemoteTransportException("Client SSL connection could not be established because trust store could not be loaded", e)
|
||||
case e: IOException ⇒ throw new RemoteTransportException("Client SSL connection could not be established because: " + e.getMessage, e)
|
||||
case e: GeneralSecurityException ⇒ throw new RemoteTransportException("Client SSL connection could not be established because SSL context could not be constructed", e)
|
||||
}
|
||||
}
|
||||
|
||||
private def initialiseServerSSL(settings: NettySettings, log: LoggingAdapter): SslHandler = {
|
||||
def initializeServerSSL(settings: NettySettings, log: LoggingAdapter): SslHandler = {
|
||||
log.debug("Server SSL is enabled, initialising ...")
|
||||
|
||||
def constructServerContext(settings: NettySettings, log: LoggingAdapter, keyStorePath: String, keyStorePassword: String, protocol: String): Option[SSLContext] =
|
||||
try {
|
||||
val factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
|
||||
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
|
||||
keyStore.load(new FileInputStream(keyStorePath), keyStorePassword.toCharArray) //FIXME does the FileInputStream need to be closed?
|
||||
factory.init(keyStore, keyStorePassword.toCharArray)
|
||||
Option(SSLContext.getInstance(protocol)) map { ctx ⇒ ctx.init(factory.getKeyManagers, null, initializeCustomSecureRandom(settings.SSLRandomNumberGenerator, settings.SSLRandomSource, log)); ctx }
|
||||
} catch {
|
||||
case e: FileNotFoundException ⇒ throw new RemoteTransportException("Server SSL connection could not be established because key store could not be loaded", e)
|
||||
case e: IOException ⇒ throw new RemoteTransportException("Server SSL connection could not be established because: " + e.getMessage, e)
|
||||
case e: GeneralSecurityException ⇒ throw new RemoteTransportException("Server SSL connection could not be established because SSL context could not be constructed", e)
|
||||
}
|
||||
|
||||
((settings.SSLKeyStore, settings.SSLKeyStorePassword, settings.SSLProtocol) match {
|
||||
case (Some(keyStore), Some(password), Some(protocol)) ⇒ constructServerContext(settings, log, keyStore, password, protocol)
|
||||
case (keyStore, password, protocol) ⇒ throw new GeneralSecurityException(
|
||||
|
|
@ -106,28 +121,14 @@ private[akka] object NettySSLSupport {
|
|||
log.debug("Using server SSL context to create SSLEngine ...")
|
||||
val sslEngine = context.createSSLEngine
|
||||
sslEngine.setUseClientMode(false)
|
||||
sslEngine.setEnabledCipherSuites(settings.SSLSupportedAlgorithms.toArray.map(_.toString))
|
||||
sslEngine.setEnabledCipherSuites(settings.SSLEnabledAlgorithms.toArray.map(_.toString))
|
||||
new SslHandler(sslEngine)
|
||||
case None ⇒ throw new GeneralSecurityException(
|
||||
"""Failed to initialise server SSL because SSL context could not be found.
|
||||
"""Failed to initialize server SSL because SSL context could not be found.
|
||||
Make sure your settings are correct: [key-store: %s] [key-store-password: %s] [protocol: %s]""".format(
|
||||
settings.SSLKeyStore,
|
||||
settings.SSLKeyStorePassword,
|
||||
settings.SSLProtocol))
|
||||
}
|
||||
}
|
||||
|
||||
private def constructServerContext(settings: NettySettings, log: LoggingAdapter, keyStorePath: String, keyStorePassword: String, protocol: String): Option[SSLContext] = {
|
||||
try {
|
||||
val factory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm)
|
||||
val keyStore = KeyStore.getInstance(KeyStore.getDefaultType)
|
||||
keyStore.load(new FileInputStream(keyStorePath), keyStorePassword.toCharArray) //FIXME does the FileInputStream need to be closed?
|
||||
factory.init(keyStore, keyStorePassword.toCharArray)
|
||||
Option(SSLContext.getInstance(protocol)) map { ctx ⇒ ctx.init(factory.getKeyManagers, null, initialiseCustomSecureRandom(settings.SSLRandomNumberGenerator, settings.SSLRandomSource, log)); ctx }
|
||||
} catch {
|
||||
case e: FileNotFoundException ⇒ throw new RemoteTransportException("Server SSL connection could not be established because key store could not be loaded", e)
|
||||
case e: IOException ⇒ throw new RemoteTransportException("Server SSL connection could not be established because: " + e.getMessage, e)
|
||||
case e: GeneralSecurityException ⇒ throw new RemoteTransportException("Server SSL connection could not be established because SSL context could not be constructed", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@ private[akka] class NettySettings(config: Config, val systemName: String) {
|
|||
case password ⇒ Some(password)
|
||||
}
|
||||
|
||||
val SSLSupportedAlgorithms = getStringList("ssl.supported-algorithms")
|
||||
val SSLEnabledAlgorithms = getStringList("ssl.enabled-algorithms").toArray.toSet
|
||||
|
||||
val SSLProtocol = getString("ssl.protocol") match {
|
||||
case "" ⇒ None
|
||||
|
|
|
|||
|
|
@ -4,11 +4,13 @@
|
|||
|
||||
package akka.actor;
|
||||
|
||||
/*
|
||||
/******************************************
|
||||
Compile with:
|
||||
cd ./akka-remote/src/test/protocol
|
||||
protoc ProtobufProtocol.proto --java_out ../java
|
||||
*/
|
||||
cd ../../../..
|
||||
./scripts/fix-protobuf.sh
|
||||
*******************************************/
|
||||
|
||||
message MyMessage {
|
||||
required uint64 id = 1;
|
||||
|
|
|
|||
|
|
@ -9,27 +9,36 @@ import com.typesafe.config._
|
|||
import akka.dispatch.{ Await, Future }
|
||||
import akka.pattern.ask
|
||||
import java.io.File
|
||||
import java.security.{ SecureRandom, PrivilegedAction, AccessController }
|
||||
import netty.NettySSLSupport
|
||||
import akka.event.{ NoLogging, LoggingAdapter }
|
||||
import java.security.{ NoSuchAlgorithmException, SecureRandom, PrivilegedAction, AccessController }
|
||||
import netty.{ NettySettings, NettySSLSupport }
|
||||
import javax.net.ssl.SSLException
|
||||
import akka.util.{ Timeout, Duration }
|
||||
import akka.util.duration._
|
||||
|
||||
object Configuration {
|
||||
// set this in your JAVA_OPTS to see all ssl debug info: "-Djavax.net.debug=ssl,keymanager"
|
||||
// The certificate will expire in 2109
|
||||
private val trustStore = getPath("truststore")
|
||||
private val keyStore = getPath("keystore")
|
||||
private def getPath(name: String): String = (new File("akka-remote/src/test/resources/" + name)).getAbsolutePath.replace("\\", "\\\\")
|
||||
private val trustStore = getClass.getClassLoader.getResource("truststore").getPath
|
||||
private val keyStore = getClass.getClassLoader.getResource("keystore").getPath
|
||||
private val conf = """
|
||||
akka {
|
||||
actor.provider = "akka.remote.RemoteActorRefProvider"
|
||||
test {
|
||||
single-expect-default = 10s
|
||||
filter-leeway = 10s
|
||||
default-timeout = 10s
|
||||
}
|
||||
|
||||
remote.netty {
|
||||
hostname = localhost
|
||||
port = 12345
|
||||
ssl {
|
||||
enable = on
|
||||
trust-store = "%s"
|
||||
key-store = "%s"
|
||||
random-number-generator = "%s"
|
||||
enabled-algorithms = [%s]
|
||||
sha1prng-random-source = "/dev/./urandom"
|
||||
}
|
||||
}
|
||||
actor.deployment {
|
||||
|
|
@ -40,71 +49,91 @@ object Configuration {
|
|||
}
|
||||
"""
|
||||
|
||||
def getCipherConfig(cipher: String): (String, Boolean, Config) = if (try {
|
||||
NettySSLSupport.initialiseCustomSecureRandom(Some(cipher), None, NoLogging) ne null
|
||||
def getCipherConfig(cipher: String, enabled: String*): (String, Boolean, Config) = try {
|
||||
|
||||
if (true) throw new IllegalArgumentException("Ticket1978*Spec isn't enabled")
|
||||
|
||||
val config = ConfigFactory.parseString("akka.remote.netty.port=12345").withFallback(ConfigFactory.parseString(conf.format(trustStore, keyStore, cipher, enabled.mkString(", "))))
|
||||
val fullConfig = config.withFallback(AkkaSpec.testConf).withFallback(ConfigFactory.load).getConfig("akka.remote.netty")
|
||||
val settings = new NettySettings(fullConfig, "placeholder")
|
||||
|
||||
val rng = NettySSLSupport.initializeCustomSecureRandom(settings.SSLRandomNumberGenerator, settings.SSLRandomSource, NoLogging)
|
||||
|
||||
rng.nextInt() // Has to work
|
||||
settings.SSLRandomNumberGenerator foreach { sRng ⇒ rng.getAlgorithm == sRng || (throw new NoSuchAlgorithmException(sRng)) }
|
||||
|
||||
val engine = NettySSLSupport.initializeClientSSL(settings, NoLogging).getEngine
|
||||
val gotAllSupported = enabled.toSet -- engine.getSupportedCipherSuites.toSet
|
||||
val gotAllEnabled = enabled.toSet -- engine.getEnabledCipherSuites.toSet
|
||||
gotAllSupported.isEmpty || (throw new IllegalArgumentException("Cipher Suite not supported: " + gotAllSupported))
|
||||
gotAllEnabled.isEmpty || (throw new IllegalArgumentException("Cipher Suite not enabled: " + gotAllEnabled))
|
||||
engine.getSupportedProtocols.contains(settings.SSLProtocol.get) || (throw new IllegalArgumentException("Protocol not supported: " + settings.SSLProtocol.get))
|
||||
|
||||
(cipher, true, config)
|
||||
} catch {
|
||||
case iae: IllegalArgumentException if iae.getMessage == "Cannot support %s with currently installed providers".format(cipher) ⇒ false
|
||||
case nsae: java.security.NoSuchAlgorithmException ⇒ false
|
||||
}) (cipher, true, ConfigFactory.parseString(conf.format(trustStore, keyStore, cipher))) else (cipher, false, AkkaSpec.testConf)
|
||||
case (_: IllegalArgumentException) | (_: NoSuchAlgorithmException) ⇒ (cipher, false, AkkaSpec.testConf) // Cannot match against the message since the message might be localized :S
|
||||
}
|
||||
}
|
||||
|
||||
import Configuration.getCipherConfig
|
||||
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class Ticket1978SHA1PRNGSpec extends Ticket1978CommunicationSpec(getCipherConfig("SHA1PRNG"))
|
||||
class Ticket1978SHA1PRNGSpec extends Ticket1978CommunicationSpec(getCipherConfig("SHA1PRNG", "TLS_RSA_WITH_AES_128_CBC_SHA"))
|
||||
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class Ticket1978AES128CounterRNGFastSpec extends Ticket1978CommunicationSpec(getCipherConfig("AES128CounterRNGFast"))
|
||||
class Ticket1978AES128CounterRNGFastSpec extends Ticket1978CommunicationSpec(getCipherConfig("AES128CounterRNGFast", "TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"))
|
||||
|
||||
/**
|
||||
* Both of the <quote>Secure</quote> variants require access to the Internet to access random.org.
|
||||
*/
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class Ticket1978AES128CounterRNGSecureSpec extends Ticket1978CommunicationSpec(getCipherConfig("AES128CounterRNGSecure"))
|
||||
class Ticket1978AES128CounterRNGSecureSpec extends Ticket1978CommunicationSpec(getCipherConfig("AES128CounterRNGSecure", "TLS_RSA_WITH_AES_128_CBC_SHA"))
|
||||
|
||||
/**
|
||||
* Both of the <quote>Secure</quote> variants require access to the Internet to access random.org.
|
||||
*/
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class Ticket1978AES256CounterRNGSecureSpec extends Ticket1978CommunicationSpec(getCipherConfig("AES256CounterRNGSecure"))
|
||||
class Ticket1978AES256CounterRNGSecureSpec extends Ticket1978CommunicationSpec(getCipherConfig("AES256CounterRNGSecure", "TLS_RSA_WITH_AES_256_CBC_SHA"))
|
||||
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class Ticket1978DefaultRNGSecureSpec extends Ticket1978CommunicationSpec(getCipherConfig("", "TLS_RSA_WITH_AES_128_CBC_SHA"))
|
||||
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
class Ticket1978NonExistingRNGSecureSpec extends Ticket1978CommunicationSpec(("NonExistingRNG", false, AkkaSpec.testConf))
|
||||
|
||||
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
||||
abstract class Ticket1978CommunicationSpec(val cipherEnabledconfig: (String, Boolean, Config)) extends AkkaSpec(cipherEnabledconfig._3) with ImplicitSender with DefaultTimeout {
|
||||
abstract class Ticket1978CommunicationSpec(val cipherEnabledconfig: (String, Boolean, Config)) extends AkkaSpec(cipherEnabledconfig._3) with ImplicitSender {
|
||||
|
||||
implicit val timeout: Timeout = Timeout(5 seconds)
|
||||
|
||||
import RemoteCommunicationSpec._
|
||||
|
||||
val conf = ConfigFactory.parseString("akka.remote.netty.port=12346").withFallback(system.settings.config)
|
||||
val other = ActorSystem("remote-sys", conf)
|
||||
|
||||
val remote = other.actorOf(Props(new Actor {
|
||||
def receive = {
|
||||
case "ping" ⇒ sender ! (("pong", sender))
|
||||
}
|
||||
}), "echo")
|
||||
|
||||
val here = system.actorFor("akka://remote-sys@localhost:12346/user/echo")
|
||||
val other = ActorSystem("remote-sys", ConfigFactory.parseString("akka.remote.netty.port=12346").withFallback(system.settings.config))
|
||||
|
||||
override def atTermination() {
|
||||
other.shutdown()
|
||||
other.awaitTermination()
|
||||
}
|
||||
|
||||
"SSL Remoting" must {
|
||||
if (cipherEnabledconfig._2) {
|
||||
val remote = other.actorOf(Props(new Actor { def receive = { case "ping" ⇒ sender ! (("pong", sender)) } }), "echo")
|
||||
|
||||
val here = system.actorFor("akka://remote-sys@localhost:12346/user/echo")
|
||||
|
||||
"support remote look-ups" in {
|
||||
here ! "ping"
|
||||
expectMsgPF() {
|
||||
expectMsgPF(timeout.duration) {
|
||||
case ("pong", s: AnyRef) if s eq testActor ⇒ true
|
||||
}
|
||||
}
|
||||
|
||||
"send error message for wrong address" in {
|
||||
"send error message for wrong address" ignore {
|
||||
within(timeout.duration) {
|
||||
EventFilter.error(start = "dropping", occurrences = 1).intercept {
|
||||
system.actorFor("akka://remotesys@localhost:12346/user/echo") ! "ping"
|
||||
}(other)
|
||||
}
|
||||
}
|
||||
|
||||
"support ask" in {
|
||||
Await.result(here ? "ping", timeout.duration) match {
|
||||
|
|
@ -114,12 +143,15 @@ abstract class Ticket1978CommunicationSpec(val cipherEnabledconfig: (String, Boo
|
|||
}
|
||||
|
||||
"send dead letters on remote if actor does not exist" in {
|
||||
within(timeout.duration) {
|
||||
EventFilter.warning(pattern = "dead.*buh", occurrences = 1).intercept {
|
||||
system.actorFor("akka://remote-sys@localhost:12346/does/not/exist") ! "buh"
|
||||
}(other)
|
||||
}
|
||||
}
|
||||
|
||||
"create and supervise children on remote node" in {
|
||||
within(timeout.duration) {
|
||||
val r = system.actorOf(Props[Echo], "blub")
|
||||
r.path.toString must be === "akka://remote-sys@localhost:12346/remote/Ticket1978CommunicationSpec@localhost:12345/user/blub"
|
||||
r ! 42
|
||||
|
|
@ -130,11 +162,11 @@ abstract class Ticket1978CommunicationSpec(val cipherEnabledconfig: (String, Boo
|
|||
expectMsg("preRestart")
|
||||
r ! 42
|
||||
expectMsg(42)
|
||||
system.stop(r)
|
||||
expectMsg("postStop")
|
||||
}
|
||||
}
|
||||
|
||||
"look-up actors across node boundaries" in {
|
||||
within(timeout.duration) {
|
||||
val l = system.actorOf(Props(new Actor {
|
||||
def receive = {
|
||||
case (p: Props, n: String) ⇒ sender ! context.actorOf(p, n)
|
||||
|
|
@ -156,10 +188,11 @@ abstract class Ticket1978CommunicationSpec(val cipherEnabledconfig: (String, Boo
|
|||
Await.result(l ? "child/..", timeout.duration).asInstanceOf[AnyRef] must be theSameInstanceAs l
|
||||
Await.result(system.actorFor(system / "looker" / "child") ? "..", timeout.duration).asInstanceOf[AnyRef] must be theSameInstanceAs l
|
||||
}
|
||||
}
|
||||
|
||||
"not fail ask across node boundaries" in {
|
||||
val f = for (_ ← 1 to 1000) yield here ? "ping" mapTo manifest[(String, ActorRef)]
|
||||
Await.result(Future.sequence(f), remaining).map(_._1).toSet must be(Set("pong"))
|
||||
Await.result(Future.sequence(f), timeout.duration).map(_._1).toSet must be(Set("pong"))
|
||||
}
|
||||
} else {
|
||||
"not be run when the cipher is not supported by the platform this test is currently being executed on" ignore {
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ akka {
|
|||
SSLTrustStore must be(Some("truststore"))
|
||||
SSLTrustStorePassword must be(Some("changeme"))
|
||||
SSLProtocol must be(Some("TLSv1"))
|
||||
SSLSupportedAlgorithms must be(java.util.Arrays.asList("TLS_RSA_WITH_AES_128_CBC_SHA", "TLS_RSA_WITH_AES_256_CBC_SHA"))
|
||||
SSLEnabledAlgorithms must be(Set("TLS_RSA_WITH_AES_128_CBC_SHA"))
|
||||
SSLRandomSource must be(None)
|
||||
SSLRandomNumberGenerator must be(None)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,7 +75,9 @@ object AkkaKernelPlugin extends Plugin {
|
|||
|
||||
copyFiles(libFiles(cp, conf.libFilter), distLibPath)
|
||||
copyFiles(conf.additionalLibs, distLibPath)
|
||||
for (subTarget ← subProjectDependencies.map(_.target)) {
|
||||
for (subProjectDependency ← subProjectDependencies) {
|
||||
val subTarget = subProjectDependency.target
|
||||
EvaluateTask(buildStruct, packageBin in Compile, st, subProjectDependency.projectRef)
|
||||
copyJars(subTarget, distLibPath)
|
||||
}
|
||||
log.info("Distribution created.")
|
||||
|
|
@ -220,10 +222,10 @@ object AkkaKernelPlugin extends Plugin {
|
|||
}.toList
|
||||
|
||||
val target = setting(Keys.crossTarget, "Missing crossTarget directory")
|
||||
SubProjectInfo(project.id, target, subProjects)
|
||||
SubProjectInfo(projectRef, target, subProjects)
|
||||
}
|
||||
|
||||
private case class SubProjectInfo(id: String, target: File, subProjects: Seq[SubProjectInfo]) {
|
||||
private case class SubProjectInfo(projectRef: ProjectRef, target: File, subProjects: Seq[SubProjectInfo]) {
|
||||
|
||||
def recursiveSubProjects: Set[SubProjectInfo] = {
|
||||
val flatSubProjects = for {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,7 @@
|
|||
package akka.testkit
|
||||
|
||||
import akka.actor._
|
||||
import akka.util.Duration
|
||||
import java.util.concurrent.atomic.AtomicLong
|
||||
import scala.collection.immutable.Stack
|
||||
import akka.dispatch._
|
||||
import akka.pattern.ask
|
||||
|
||||
|
|
|
|||
|
|
@ -158,7 +158,13 @@ trait TestKitBase {
|
|||
* block or missing that it returns the properly dilated default for this
|
||||
* case from settings (key "akka.test.single-expect-default").
|
||||
*/
|
||||
def remaining: Duration = if (end == Duration.Undefined) testKitSettings.SingleExpectDefaultTimeout.dilated else end - now
|
||||
def remaining: Duration = remainingOr(testKitSettings.SingleExpectDefaultTimeout.dilated)
|
||||
|
||||
/**
|
||||
* Obtain time remaining for execution of the innermost enclosing `within`
|
||||
* block or missing that it returns the given duration.
|
||||
*/
|
||||
def remainingOr(duration: Duration): Duration = if (end == Duration.Undefined) duration else end - now
|
||||
|
||||
/**
|
||||
* Query queue status.
|
||||
|
|
@ -486,7 +492,8 @@ trait TestKitBase {
|
|||
|
||||
@tailrec
|
||||
def doit(acc: List[T], count: Int): List[T] = {
|
||||
if (count >= messages) return acc.reverse
|
||||
if (count >= messages) acc.reverse
|
||||
else {
|
||||
receiveOne((stop - now) min idle)
|
||||
lastMessage match {
|
||||
case NullMessage ⇒
|
||||
|
|
@ -501,6 +508,7 @@ trait TestKitBase {
|
|||
acc.reverse
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val ret = doit(Nil, 0)
|
||||
lastWasNoMsg = true
|
||||
|
|
@ -605,12 +613,6 @@ object TestKit {
|
|||
/**
|
||||
* Await until the given condition evaluates to `true` or the timeout
|
||||
* expires, whichever comes first.
|
||||
*
|
||||
* If no timeout is given, take it from the innermost enclosing `within`
|
||||
* block.
|
||||
*
|
||||
* Note that the timeout is scaled using Duration.dilated, which uses the
|
||||
* configuration entry "akka.test.timefactor"
|
||||
*/
|
||||
def awaitCond(p: ⇒ Boolean, max: Duration, interval: Duration = 100.millis, noThrow: Boolean = false): Boolean = {
|
||||
val stop = now + max
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ public class UntypedCoordinatedIncrementTest {
|
|||
Timeout timeout = new Timeout(timeoutSeconds, TimeUnit.SECONDS);
|
||||
|
||||
@Before
|
||||
public void initialise() {
|
||||
public void initialize() {
|
||||
counters = new ArrayList<ActorRef>();
|
||||
for (int i = 1; i <= numCounters; i++) {
|
||||
final String name = "counter" + i;
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ public class UntypedTransactorTest {
|
|||
Timeout timeout = new Timeout(timeoutSeconds, TimeUnit.SECONDS);
|
||||
|
||||
@Before
|
||||
public void initialise() {
|
||||
public void initialize() {
|
||||
counters = new ArrayList<ActorRef>();
|
||||
for (int i = 1; i <= numCounters; i++) {
|
||||
final String name = "counter" + i;
|
||||
|
|
|
|||
|
|
@ -9,14 +9,17 @@ import akka.actor._
|
|||
import akka.dispatch.{ Promise, Future }
|
||||
import akka.event.Logging
|
||||
import annotation.tailrec
|
||||
import akka.util.Duration
|
||||
import java.util.concurrent.TimeUnit
|
||||
import collection.mutable.ListBuffer
|
||||
import akka.util.{ NonFatal, Duration }
|
||||
|
||||
private[zeromq] object ConcurrentSocketActor {
|
||||
private sealed trait PollMsg
|
||||
private case object Poll extends PollMsg
|
||||
private case object PollCareful extends PollMsg
|
||||
|
||||
private case object Flush
|
||||
|
||||
private class NoSocketHandleException() extends Exception("Couldn't create a zeromq socket.")
|
||||
|
||||
private val DefaultContext = Context()
|
||||
|
|
@ -32,19 +35,28 @@ private[zeromq] class ConcurrentSocketActor(params: Seq[SocketOption]) extends A
|
|||
import SocketType.{ ZMQSocketType ⇒ ST }
|
||||
params.collectFirst { case t: ST ⇒ t }.getOrElse(throw new IllegalArgumentException("A socket type is required"))
|
||||
}
|
||||
|
||||
private val socket: Socket = zmqContext.socket(socketType)
|
||||
private val poller: Poller = zmqContext.poller
|
||||
private val log = Logging(context.system, this)
|
||||
|
||||
private val pendingSends = new ListBuffer[Seq[Frame]]
|
||||
|
||||
def receive = {
|
||||
case m: PollMsg ⇒ doPoll(m)
|
||||
case ZMQMessage(frames) ⇒ sendMessage(frames)
|
||||
case ZMQMessage(frames) ⇒ handleRequest(Send(frames))
|
||||
case r: Request ⇒ handleRequest(r)
|
||||
case Flush ⇒ flush()
|
||||
case Terminated(_) ⇒ context stop self
|
||||
}
|
||||
|
||||
private def handleRequest(msg: Request): Unit = msg match {
|
||||
case Send(frames) ⇒ sendMessage(frames)
|
||||
case Send(frames) ⇒
|
||||
if (frames.nonEmpty) {
|
||||
val flushNow = pendingSends.isEmpty
|
||||
pendingSends.append(frames)
|
||||
if (flushNow) flush()
|
||||
}
|
||||
case opt: SocketOption ⇒ handleSocketOption(opt)
|
||||
case q: SocketOptionQuery ⇒ handleSocketOptionQuery(q)
|
||||
}
|
||||
|
|
@ -117,48 +129,46 @@ private[zeromq] class ConcurrentSocketActor(params: Seq[SocketOption]) extends A
|
|||
}
|
||||
}
|
||||
|
||||
private def setupConnection() {
|
||||
private def setupConnection(): Unit = {
|
||||
params filter (_.isInstanceOf[SocketConnectOption]) foreach { self ! _ }
|
||||
params filter (_.isInstanceOf[PubSubOption]) foreach { self ! _ }
|
||||
}
|
||||
|
||||
private def deserializerFromParams = {
|
||||
private def deserializerFromParams: Deserializer =
|
||||
params collectFirst { case d: Deserializer ⇒ d } getOrElse new ZMQMessageDeserializer
|
||||
}
|
||||
|
||||
private def setupSocket() = {
|
||||
params foreach {
|
||||
private def setupSocket() = params foreach {
|
||||
case _: SocketConnectOption | _: PubSubOption | _: SocketMeta ⇒ // ignore, handled differently
|
||||
case m ⇒ self ! m
|
||||
}
|
||||
}
|
||||
|
||||
override def preRestart(reason: Throwable, message: Option[Any]) {
|
||||
context.children foreach context.stop //Do not call postStop
|
||||
}
|
||||
override def preRestart(reason: Throwable, message: Option[Any]): Unit = context.children foreach context.stop //Do not call postStop
|
||||
|
||||
override def postRestart(reason: Throwable) {} //Do nothing
|
||||
override def postRestart(reason: Throwable): Unit = () // Do nothing
|
||||
|
||||
override def postStop {
|
||||
try {
|
||||
override def postStop: Unit = try {
|
||||
if (socket != null) {
|
||||
poller.unregister(socket)
|
||||
socket.close
|
||||
}
|
||||
} finally {
|
||||
notifyListener(Closed)
|
||||
} finally notifyListener(Closed)
|
||||
|
||||
@tailrec private def flushMessage(i: Seq[Frame]): Boolean =
|
||||
if (i.isEmpty)
|
||||
true
|
||||
else {
|
||||
val head = i.head
|
||||
val tail = i.tail
|
||||
if (socket.send(head.payload.toArray, if (tail.nonEmpty) JZMQ.SNDMORE else 0)) flushMessage(tail)
|
||||
else {
|
||||
pendingSends.prepend(i) // Reenqueue the rest of the message so the next flush takes care of it
|
||||
self ! Flush
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private def sendMessage(frames: Seq[Frame]) {
|
||||
def sendBytes(bytes: Seq[Byte], flags: Int) = socket.send(bytes.toArray, flags)
|
||||
val iter = frames.iterator
|
||||
while (iter.hasNext) {
|
||||
val payload = iter.next.payload
|
||||
val flags = if (iter.hasNext) JZMQ.SNDMORE else 0
|
||||
sendBytes(payload, flags)
|
||||
}
|
||||
}
|
||||
@tailrec private def flush(): Unit =
|
||||
if (pendingSends.nonEmpty && flushMessage(pendingSends.remove(0))) flush() // Flush while things are going well
|
||||
|
||||
// this is a “PollMsg=>Unit” which either polls or schedules Poll, depending on the sign of the timeout
|
||||
private val doPollTimeout = {
|
||||
|
|
|
|||
|
|
@ -139,8 +139,7 @@ class ZeroMQExtension(system: ActorSystem) extends Extension {
|
|||
*/
|
||||
def newSocket(socketParameters: SocketOption*): ActorRef = {
|
||||
implicit val timeout = NewSocketTimeout
|
||||
val req = (zeromqGuardian ? newSocketProps(socketParameters: _*)).mapTo[ActorRef]
|
||||
Await.result(req, timeout.duration)
|
||||
Await.result((zeromqGuardian ? newSocketProps(socketParameters: _*)).mapTo[ActorRef], timeout.duration)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -248,9 +247,7 @@ class ZeroMQExtension(system: ActorSystem) extends Extension {
|
|||
case _ ⇒ false
|
||||
}
|
||||
|
||||
def receive = {
|
||||
case p: Props ⇒ sender ! context.actorOf(p)
|
||||
}
|
||||
def receive = { case p: Props ⇒ sender ! context.actorOf(p) }
|
||||
}), "zeromq")
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue