2012-06-15 13:33:58 +02:00
|
|
|
/**
|
2015-03-07 22:58:48 -08:00
|
|
|
* Copyright (C) 2009-2015 Typesafe Inc. <http://www.typesafe.com>
|
2012-06-15 13:33:58 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package akka.cluster
|
|
|
|
|
|
2012-07-26 14:47:21 +02:00
|
|
|
import language.implicitConversions
|
|
|
|
|
|
2012-06-15 13:33:58 +02:00
|
|
|
import com.typesafe.config.ConfigFactory
|
|
|
|
|
import akka.remote.testkit.MultiNodeConfig
|
|
|
|
|
import akka.remote.testkit.MultiNodeSpec
|
|
|
|
|
import akka.testkit._
|
|
|
|
|
import akka.actor.Address
|
|
|
|
|
import akka.remote.testconductor.RoleName
|
|
|
|
|
import MemberStatus._
|
2012-07-04 11:37:56 +02:00
|
|
|
import InternalClusterAction._
|
2012-06-15 13:33:58 +02:00
|
|
|
|
|
|
|
|
object TransitionMultiJvmSpec extends MultiNodeConfig {
|
|
|
|
|
val first = role("first")
|
|
|
|
|
val second = role("second")
|
|
|
|
|
val third = role("third")
|
|
|
|
|
|
|
|
|
|
commonConfig(debugConfig(on = false).
|
2013-05-27 16:34:31 +02:00
|
|
|
withFallback(ConfigFactory.parseString("""
|
|
|
|
|
akka.cluster.periodic-tasks-initial-delay = 300 s # turn off all periodic tasks
|
|
|
|
|
akka.cluster.publish-stats-interval = 0 s # always, when it happens
|
|
|
|
|
""")).
|
2012-09-06 21:48:40 +02:00
|
|
|
withFallback(MultiNodeClusterSpec.clusterConfigWithFailureDetectorPuppet))
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2012-09-06 21:48:40 +02:00
|
|
|
class TransitionMultiJvmNode1 extends TransitionSpec
|
|
|
|
|
class TransitionMultiJvmNode2 extends TransitionSpec
|
|
|
|
|
class TransitionMultiJvmNode3 extends TransitionSpec
|
2012-06-15 13:33:58 +02:00
|
|
|
|
|
|
|
|
abstract class TransitionSpec
|
|
|
|
|
extends MultiNodeSpec(TransitionMultiJvmSpec)
|
2012-07-04 11:37:56 +02:00
|
|
|
with MultiNodeClusterSpec
|
|
|
|
|
with ImplicitSender {
|
2012-06-15 13:33:58 +02:00
|
|
|
|
|
|
|
|
import TransitionMultiJvmSpec._
|
|
|
|
|
|
2012-10-01 20:08:21 +02:00
|
|
|
muteMarkingAsUnreachable()
|
|
|
|
|
|
2012-06-15 13:33:58 +02:00
|
|
|
// sorted in the order used by the cluster
|
|
|
|
|
def leader(roles: RoleName*) = roles.sorted.head
|
|
|
|
|
def nonLeader(roles: RoleName*) = roles.toSeq.sorted.tail
|
|
|
|
|
|
|
|
|
|
def memberStatus(address: Address): MemberStatus = {
|
2012-08-16 18:28:01 +02:00
|
|
|
val statusOption = (clusterView.members ++ clusterView.unreachableMembers).collectFirst {
|
2012-06-15 13:33:58 +02:00
|
|
|
case m if m.address == address ⇒ m.status
|
|
|
|
|
}
|
2013-03-05 21:05:11 +01:00
|
|
|
statusOption.getOrElse(Removed)
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2012-08-16 18:28:01 +02:00
|
|
|
def memberAddresses: Set[Address] = clusterView.members.map(_.address)
|
2012-06-15 13:33:58 +02:00
|
|
|
|
|
|
|
|
def members: Set[RoleName] = memberAddresses.flatMap(roleName(_))
|
|
|
|
|
|
2012-08-16 18:28:01 +02:00
|
|
|
def seenLatestGossip: Set[RoleName] = clusterView.seenBy flatMap roleName
|
2012-06-15 13:33:58 +02:00
|
|
|
|
2013-03-24 22:01:57 +01:00
|
|
|
def awaitSeen(addresses: Address*): Unit = awaitAssert {
|
2015-01-16 11:09:59 +01:00
|
|
|
(seenLatestGossip map address) should ===(addresses.toSet)
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2013-03-24 22:01:57 +01:00
|
|
|
def awaitMembers(addresses: Address*): Unit = awaitAssert {
|
2013-03-05 21:05:11 +01:00
|
|
|
clusterView.refreshCurrentState()
|
2015-01-16 11:09:59 +01:00
|
|
|
memberAddresses should ===(addresses.toSet)
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2013-03-24 22:01:57 +01:00
|
|
|
def awaitMemberStatus(address: Address, status: MemberStatus): Unit = awaitAssert {
|
2013-03-05 21:05:11 +01:00
|
|
|
clusterView.refreshCurrentState()
|
2015-01-16 11:09:59 +01:00
|
|
|
memberStatus(address) should ===(status)
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2012-09-12 09:23:02 +02:00
|
|
|
def leaderActions(): Unit =
|
2012-07-04 11:37:56 +02:00
|
|
|
cluster.clusterCore ! LeaderActionsTick
|
|
|
|
|
|
2012-09-12 09:23:02 +02:00
|
|
|
def reapUnreachable(): Unit =
|
2012-07-04 11:37:56 +02:00
|
|
|
cluster.clusterCore ! ReapUnreachableTick
|
|
|
|
|
|
2012-06-15 13:33:58 +02:00
|
|
|
// DSL sugar for `role1 gossipTo role2`
|
|
|
|
|
implicit def roleExtras(role: RoleName): RoleWrapper = new RoleWrapper(role)
|
|
|
|
|
var gossipBarrierCounter = 0
|
|
|
|
|
class RoleWrapper(fromRole: RoleName) {
|
|
|
|
|
def gossipTo(toRole: RoleName): Unit = {
|
|
|
|
|
gossipBarrierCounter += 1
|
|
|
|
|
runOn(toRole) {
|
2013-05-27 16:34:31 +02:00
|
|
|
val oldCount = clusterView.latestStats.gossipStats.receivedGossipCount
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("before-gossip-" + gossipBarrierCounter)
|
2012-08-15 16:47:34 +02:00
|
|
|
awaitCond {
|
2013-05-27 16:34:31 +02:00
|
|
|
clusterView.latestStats.gossipStats.receivedGossipCount != oldCount // received gossip
|
2012-08-15 16:47:34 +02:00
|
|
|
}
|
2012-06-28 11:36:13 +02:00
|
|
|
// gossip chat will synchronize the views
|
|
|
|
|
awaitCond((Set(fromRole, toRole) -- seenLatestGossip).isEmpty)
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("after-gossip-" + gossipBarrierCounter)
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
runOn(fromRole) {
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("before-gossip-" + gossipBarrierCounter)
|
2012-07-04 11:37:56 +02:00
|
|
|
// send gossip
|
|
|
|
|
cluster.clusterCore ! InternalClusterAction.SendGossipTo(toRole)
|
2012-06-28 11:36:13 +02:00
|
|
|
// gossip chat will synchronize the views
|
|
|
|
|
awaitCond((Set(fromRole, toRole) -- seenLatestGossip).isEmpty)
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("after-gossip-" + gossipBarrierCounter)
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
runOn(roles.filterNot(r ⇒ r == fromRole || r == toRole): _*) {
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("before-gossip-" + gossipBarrierCounter)
|
|
|
|
|
enterBarrier("after-gossip-" + gossipBarrierCounter)
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"A Cluster" must {
|
|
|
|
|
|
|
|
|
|
"start nodes as singleton clusters" taggedAs LongRunningTest in {
|
|
|
|
|
|
2012-06-26 18:19:29 +02:00
|
|
|
runOn(first) {
|
2013-03-05 21:05:11 +01:00
|
|
|
cluster join myself
|
2015-10-21 07:53:12 +02:00
|
|
|
// first joining itself will immediately be moved to Up
|
2012-08-21 17:51:49 +02:00
|
|
|
awaitMemberStatus(myself, Up)
|
2013-03-05 21:05:11 +01:00
|
|
|
awaitCond(clusterView.isSingletonCluster)
|
2012-06-26 18:19:29 +02:00
|
|
|
}
|
2012-06-15 13:33:58 +02:00
|
|
|
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("after-1")
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"perform correct transitions when second joining first" taggedAs LongRunningTest in {
|
|
|
|
|
|
|
|
|
|
runOn(second) {
|
|
|
|
|
cluster.join(first)
|
|
|
|
|
}
|
|
|
|
|
runOn(first, second) {
|
2012-06-28 11:36:13 +02:00
|
|
|
// gossip chat from the join will synchronize the views
|
|
|
|
|
awaitMembers(first, second)
|
2012-08-21 17:51:49 +02:00
|
|
|
awaitMemberStatus(first, Up)
|
|
|
|
|
awaitMemberStatus(second, Joining)
|
2015-01-16 11:09:59 +01:00
|
|
|
awaitAssert(seenLatestGossip should ===(Set(first, second)))
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("convergence-joining-2")
|
2012-06-15 13:33:58 +02:00
|
|
|
|
2013-03-08 09:05:51 +01:00
|
|
|
runOn(first) {
|
2012-07-04 11:37:56 +02:00
|
|
|
leaderActions()
|
2012-08-21 17:51:49 +02:00
|
|
|
awaitMemberStatus(first, Up)
|
2013-03-20 13:29:19 +01:00
|
|
|
awaitMemberStatus(second, Up)
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("leader-actions-2")
|
2012-06-15 13:33:58 +02:00
|
|
|
|
2013-03-08 09:05:51 +01:00
|
|
|
first gossipTo second
|
2012-06-15 13:33:58 +02:00
|
|
|
runOn(first, second) {
|
2012-06-28 11:36:13 +02:00
|
|
|
// gossip chat will synchronize the views
|
2012-08-21 17:51:49 +02:00
|
|
|
awaitMemberStatus(second, Up)
|
2015-01-16 11:09:59 +01:00
|
|
|
awaitAssert(seenLatestGossip should ===(Set(first, second)))
|
2012-08-21 17:51:49 +02:00
|
|
|
awaitMemberStatus(first, Up)
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("after-2")
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"perform correct transitions when third joins second" taggedAs LongRunningTest in {
|
|
|
|
|
|
|
|
|
|
runOn(third) {
|
|
|
|
|
cluster.join(second)
|
|
|
|
|
}
|
2012-06-28 11:36:13 +02:00
|
|
|
runOn(second, third) {
|
|
|
|
|
// gossip chat from the join will synchronize the views
|
2015-01-16 11:09:59 +01:00
|
|
|
awaitAssert(seenLatestGossip should ===(Set(second, third)))
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("third-joined-second")
|
2012-06-15 13:33:58 +02:00
|
|
|
|
|
|
|
|
second gossipTo first
|
2012-06-28 11:36:13 +02:00
|
|
|
runOn(first, second) {
|
|
|
|
|
// gossip chat will synchronize the views
|
|
|
|
|
awaitMembers(first, second, third)
|
2012-08-21 17:51:49 +02:00
|
|
|
awaitMemberStatus(third, Joining)
|
|
|
|
|
awaitMemberStatus(second, Up)
|
2015-01-16 11:09:59 +01:00
|
|
|
awaitAssert(seenLatestGossip should ===(Set(first, second, third)))
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2012-06-19 16:00:45 +02:00
|
|
|
first gossipTo third
|
2012-06-15 13:33:58 +02:00
|
|
|
runOn(first, second, third) {
|
2012-08-21 17:51:49 +02:00
|
|
|
awaitMembers(first, second, third)
|
|
|
|
|
awaitMemberStatus(first, Up)
|
|
|
|
|
awaitMemberStatus(second, Up)
|
|
|
|
|
awaitMemberStatus(third, Joining)
|
2015-01-16 11:09:59 +01:00
|
|
|
awaitAssert(seenLatestGossip should ===(Set(first, second, third)))
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("convergence-joining-3")
|
2012-06-15 13:33:58 +02:00
|
|
|
|
2013-03-08 09:05:51 +01:00
|
|
|
val leader12 = leader(first, second)
|
|
|
|
|
val (other1, other2) = { val tmp = roles.filterNot(_ == leader12); (tmp.head, tmp.tail.head) }
|
|
|
|
|
runOn(leader12) {
|
2012-07-04 11:37:56 +02:00
|
|
|
leaderActions()
|
2012-08-21 17:51:49 +02:00
|
|
|
awaitMemberStatus(first, Up)
|
|
|
|
|
awaitMemberStatus(second, Up)
|
2013-03-05 21:05:11 +01:00
|
|
|
awaitMemberStatus(third, Up)
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("leader-actions-3")
|
2012-06-15 13:33:58 +02:00
|
|
|
|
|
|
|
|
// leader gossipTo first non-leader
|
2013-03-08 09:05:51 +01:00
|
|
|
leader12 gossipTo other1
|
|
|
|
|
runOn(other1) {
|
2013-03-05 21:05:11 +01:00
|
|
|
awaitMemberStatus(third, Up)
|
2015-01-16 11:09:59 +01:00
|
|
|
awaitAssert(seenLatestGossip should ===(Set(leader12, myself)))
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// first non-leader gossipTo the other non-leader
|
2013-03-08 09:05:51 +01:00
|
|
|
other1 gossipTo other2
|
|
|
|
|
runOn(other1) {
|
2012-07-04 11:37:56 +02:00
|
|
|
// send gossip
|
2013-03-08 09:05:51 +01:00
|
|
|
cluster.clusterCore ! InternalClusterAction.SendGossipTo(other2)
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
2013-03-08 09:05:51 +01:00
|
|
|
runOn(other2) {
|
2012-08-21 17:51:49 +02:00
|
|
|
awaitMemberStatus(third, Up)
|
2015-01-16 11:09:59 +01:00
|
|
|
awaitAssert(seenLatestGossip should ===(Set(first, second, third)))
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// first non-leader gossipTo the leader
|
2013-03-08 09:05:51 +01:00
|
|
|
other1 gossipTo leader12
|
2012-06-15 13:33:58 +02:00
|
|
|
runOn(first, second, third) {
|
2012-08-21 17:51:49 +02:00
|
|
|
awaitMemberStatus(first, Up)
|
|
|
|
|
awaitMemberStatus(second, Up)
|
|
|
|
|
awaitMemberStatus(third, Up)
|
2015-01-16 11:09:59 +01:00
|
|
|
awaitAssert(seenLatestGossip should ===(Set(first, second, third)))
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("after-3")
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2012-06-25 08:32:55 +02:00
|
|
|
"perform correct transitions when second becomes unavailble" taggedAs LongRunningTest in {
|
2012-06-28 11:36:13 +02:00
|
|
|
runOn(third) {
|
2012-06-15 13:33:58 +02:00
|
|
|
markNodeAsUnavailable(second)
|
2012-07-04 11:37:56 +02:00
|
|
|
reapUnreachable()
|
2013-12-17 14:25:56 +01:00
|
|
|
awaitAssert(clusterView.unreachableMembers.map(_.address) should contain(address(second)))
|
2015-01-16 11:09:59 +01:00
|
|
|
awaitAssert(seenLatestGossip should ===(Set(third)))
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2012-06-20 11:06:47 +02:00
|
|
|
enterBarrier("after-second-unavailble")
|
2012-06-20 09:19:09 +02:00
|
|
|
|
2012-06-28 11:36:13 +02:00
|
|
|
third gossipTo first
|
2012-06-15 13:33:58 +02:00
|
|
|
|
2012-06-28 11:36:13 +02:00
|
|
|
runOn(first, third) {
|
2013-12-17 14:25:56 +01:00
|
|
|
awaitAssert(clusterView.unreachableMembers.map(_.address) should contain(address(second)))
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2012-06-28 11:36:13 +02:00
|
|
|
runOn(first) {
|
2012-06-15 13:33:58 +02:00
|
|
|
cluster.down(second)
|
|
|
|
|
}
|
|
|
|
|
|
2012-06-20 11:06:47 +02:00
|
|
|
enterBarrier("after-second-down")
|
2012-06-18 11:54:44 +02:00
|
|
|
|
2012-06-28 11:36:13 +02:00
|
|
|
first gossipTo third
|
2012-06-15 13:33:58 +02:00
|
|
|
|
2012-06-28 11:36:13 +02:00
|
|
|
runOn(first, third) {
|
2013-12-17 14:25:56 +01:00
|
|
|
awaitAssert(clusterView.unreachableMembers.map(_.address) should contain(address(second)))
|
2012-08-21 17:51:49 +02:00
|
|
|
awaitMemberStatus(second, Down)
|
2015-01-16 11:09:59 +01:00
|
|
|
awaitAssert(seenLatestGossip should ===(Set(first, third)))
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
2012-06-19 17:10:03 +02:00
|
|
|
enterBarrier("after-6")
|
2012-06-15 13:33:58 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|