From 9d1cbdc60112ea74b328f73e8665af9cba3ae7a0 Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Thu, 31 May 2012 14:47:43 +0200 Subject: [PATCH] Singleton cluster convergence, see #2117 --- .../src/main/scala/akka/cluster/Cluster.scala | 7 +- .../scala/akka/cluster/NodeShutdownSpec.scala | 69 +++++++++++++++++++ .../scala/akka/cluster/NodeStartupSpec.scala | 14 +--- akka-docs/cluster/cluster.rst | 10 --- 4 files changed, 75 insertions(+), 25 deletions(-) create mode 100644 akka-cluster/src/multi-jvm/scala/akka/cluster/NodeShutdownSpec.scala diff --git a/akka-cluster/src/main/scala/akka/cluster/Cluster.scala b/akka-cluster/src/main/scala/akka/cluster/Cluster.scala index a26befb875..fb3c45bec8 100644 --- a/akka-cluster/src/main/scala/akka/cluster/Cluster.scala +++ b/akka-cluster/src/main/scala/akka/cluster/Cluster.scala @@ -816,9 +816,12 @@ class Cluster(system: ExtendedActorSystem) extends Extension { clusterNode ⇒ private def gossip(): Unit = { val localState = state.get - if (!isSingletonCluster(localState) && isAvailable(localState)) { - // only gossip if we are a non-singleton cluster and available + if (isSingletonCluster(localState)) { + // gossip to myself + // TODO could perhaps be optimized, no need to gossip to myself when Up? + gossipTo(remoteAddress) + } else if (isAvailable(localState)) { log.debug("Cluster Node [{}] - Initiating new round of gossip", remoteAddress) val localGossip = localState.latestGossip diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/NodeShutdownSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/NodeShutdownSpec.scala new file mode 100644 index 0000000000..61a9c08ceb --- /dev/null +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/NodeShutdownSpec.scala @@ -0,0 +1,69 @@ +/** + * Copyright (C) 2009-2012 Typesafe Inc. + */ +package akka.cluster + +import com.typesafe.config.ConfigFactory +import org.scalatest.BeforeAndAfter +import akka.remote.testkit.MultiNodeConfig +import akka.remote.testkit.MultiNodeSpec +import akka.testkit._ +import akka.util.duration._ + +object NodeShutdownMultiJvmSpec extends MultiNodeConfig { + val first = role("first") + val second = role("second") + + commonConfig(debugConfig(on = false). + withFallback(ConfigFactory.parseString(""" + akka.cluster { + auto-down = on + failure-detector.threshold = 4 + } + """)). + withFallback(MultiNodeClusterSpec.clusterConfig)) + +} + +class NodeShutdownMultiJvmNode1 extends NodeShutdownSpec +class NodeShutdownMultiJvmNode2 extends NodeShutdownSpec + +abstract class NodeShutdownSpec extends MultiNodeSpec(NodeShutdownMultiJvmSpec) with MultiNodeClusterSpec with ImplicitSender with BeforeAndAfter { + import NodeShutdownMultiJvmSpec._ + + override def initialParticipants = 2 + + after { + testConductor.enter("after") + } + + "A cluster of 2 nodes" must { + + "not be singleton cluster when joined" taggedAs LongRunningTest in { + // make sure that the node-to-join is started before other join + runOn(first) { + cluster.self + } + testConductor.enter("first-started") + + runOn(second) { + cluster.join(node(first).address) + } + awaitUpConvergence(numberOfMembers = 2) + cluster.isSingletonCluster must be(false) + } + + "become singleton cluster when one node is shutdown" in { + runOn(first) { + val secondAddress = node(second).address + testConductor.shutdown(first, 0) + testConductor.removeNode(first) + awaitUpConvergence(numberOfMembers = 1, canNotBePartOfMemberRing = Seq(secondAddress), 30.seconds) + cluster.isSingletonCluster must be(true) + cluster.isLeader must be(true) + } + + } + } + +} diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/NodeStartupSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/NodeStartupSpec.scala index fcbcce746f..b198d1d72d 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/NodeStartupSpec.scala +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/NodeStartupSpec.scala @@ -37,19 +37,7 @@ abstract class NodeStartupSpec extends MultiNodeSpec(NodeStartupMultiJvmSpec) wi "be a singleton cluster when started up" taggedAs LongRunningTest in { runOn(first) { awaitCond(cluster.isSingletonCluster) - // FIXME #2117 singletonCluster should reach convergence - //awaitCond(cluster.convergence.isDefined) - } - } - - "be in 'Joining' phase when started up" taggedAs LongRunningTest in { - runOn(first) { - val members = cluster.latestGossip.members - members.size must be(1) - - val joiningMember = members find (_.address == firstAddress) - joiningMember must not be (None) - joiningMember.get.status must be(MemberStatus.Joining) + awaitUpConvergence(numberOfMembers = 1) } } } diff --git a/akka-docs/cluster/cluster.rst b/akka-docs/cluster/cluster.rst index 231830cecb..a0aca11114 100644 --- a/akka-docs/cluster/cluster.rst +++ b/akka-docs/cluster/cluster.rst @@ -81,16 +81,6 @@ can later explicitly send a ``Join`` message to another node to form a N-node cluster. It is also possible to link multiple N-node clusters by ``joining`` them. -Singleton Cluster ------------------ - -If a node does not have a preconfigured contact point to join in the Akka -configuration, then it is considered a singleton cluster (single node cluster) -and will automatically transition from ``joining`` to ``up``. Singleton clusters -can later explicitly send a ``Join`` message to another node to form a N-node -cluster. It is also possible to link multiple N-node clusters by ``joining`` them. - - Gossip ------