From c08bc317e26c7467f091486243d7b69348be1370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Veiga=20Ortiz=2C=20He=CC=81ctor?= Date: Tue, 25 Aug 2015 17:20:05 -0500 Subject: [PATCH] +clu #13584 Accept joining to be WeaklyUp during network split * experimental feature, disabled by default * Adding documentation to mention weakly up members. plus adding new diagram. --- .../metrics/ClusterMetricsCollector.scala | 2 + .../cluster/protobuf/msg/ClusterMessages.java | 14 ++- .../src/main/protobuf/ClusterMessages.proto | 1 + .../src/main/resources/reference.conf | 9 +- .../scala/akka/cluster/ClusterDaemon.scala | 44 ++++++- .../scala/akka/cluster/ClusterEvent.scala | 15 ++- .../scala/akka/cluster/ClusterHeartbeat.scala | 3 +- .../akka/cluster/ClusterRemoteWatcher.scala | 38 +++--- .../scala/akka/cluster/ClusterSettings.scala | 2 + .../src/main/scala/akka/cluster/Member.scala | 30 +++-- .../protobuf/ClusterMessageSerializer.scala | 3 +- .../cluster/routing/ClusterRouterConfig.scala | 2 +- .../akka/cluster/MemberWeaklyUpSpec.scala | 119 ++++++++++++++++++ .../akka/cluster/MinMembersBeforeUpSpec.scala | 28 +++++ akka-docs/rst/common/cluster.rst | 17 ++- .../rst/images/member-states-weakly-up.png | Bin 0 -> 43785 bytes akka-docs/rst/java/cluster-usage.rst | 21 ++++ akka-docs/rst/scala/cluster-usage.rst | 21 ++++ project/MiMa.scala | 5 +- 19 files changed, 329 insertions(+), 45 deletions(-) create mode 100644 akka-cluster/src/multi-jvm/scala/akka/cluster/MemberWeaklyUpSpec.scala create mode 100644 akka-docs/rst/images/member-states-weakly-up.png diff --git a/akka-cluster-metrics/src/main/scala/akka/cluster/metrics/ClusterMetricsCollector.scala b/akka-cluster-metrics/src/main/scala/akka/cluster/metrics/ClusterMetricsCollector.scala index f8eaa97f82..7d6cbedfe3 100644 --- a/akka-cluster-metrics/src/main/scala/akka/cluster/metrics/ClusterMetricsCollector.scala +++ b/akka-cluster-metrics/src/main/scala/akka/cluster/metrics/ClusterMetricsCollector.scala @@ -122,6 +122,7 @@ private[metrics] class ClusterMetricsCollector extends Actor with ActorLogging { // TODO collapse to ClusterEvent._ after akka-cluster metrics is gone import ClusterEvent.MemberEvent import ClusterEvent.MemberUp + import ClusterEvent.MemberWeaklyUp import ClusterEvent.MemberRemoved import ClusterEvent.MemberExited import ClusterEvent.ReachabilityEvent @@ -174,6 +175,7 @@ private[metrics] class ClusterMetricsCollector extends Actor with ActorLogging { case msg: MetricsGossipEnvelope ⇒ receiveGossip(msg) case state: CurrentClusterState ⇒ receiveState(state) case MemberUp(m) ⇒ addMember(m) + case MemberWeaklyUp(m) ⇒ addMember(m) case MemberRemoved(m, _) ⇒ removeMember(m) case MemberExited(m) ⇒ removeMember(m) case UnreachableMember(m) ⇒ removeMember(m) diff --git a/akka-cluster/src/main/java/akka/cluster/protobuf/msg/ClusterMessages.java b/akka-cluster/src/main/java/akka/cluster/protobuf/msg/ClusterMessages.java index fbc9320e46..9793fa2616 100644 --- a/akka-cluster/src/main/java/akka/cluster/protobuf/msg/ClusterMessages.java +++ b/akka-cluster/src/main/java/akka/cluster/protobuf/msg/ClusterMessages.java @@ -138,6 +138,10 @@ public final class ClusterMessages { * Removed = 5; */ Removed(5, 5), + /** + * WeaklyUp = 6; + */ + WeaklyUp(6, 6), ; /** @@ -164,6 +168,10 @@ public final class ClusterMessages { * Removed = 5; */ public static final int Removed_VALUE = 5; + /** + * WeaklyUp = 6; + */ + public static final int WeaklyUp_VALUE = 6; public final int getNumber() { return value; } @@ -176,6 +184,7 @@ public final class ClusterMessages { case 3: return Exiting; case 4: return Down; case 5: return Removed; + case 6: return WeaklyUp; default: return null; } } @@ -16783,10 +16792,11 @@ public final class ClusterMessages { "ort\030\003 \002(\r\022\020\n\010protocol\030\004 \001(\t\"7\n\rUniqueAdd" + "ress\022\031\n\007address\030\001 \002(\0132\010.Address\022\013\n\003uid\030\002" + " \002(\r*D\n\022ReachabilityStatus\022\r\n\tReachable\020" + - "\000\022\017\n\013Unreachable\020\001\022\016\n\nTerminated\020\002*T\n\014Me" + + "\000\022\017\n\013Unreachable\020\001\022\016\n\nTerminated\020\002*b\n\014Me" + "mberStatus\022\013\n\007Joining\020\000\022\006\n\002Up\020\001\022\013\n\007Leavi" + "ng\020\002\022\013\n\007Exiting\020\003\022\010\n\004Down\020\004\022\013\n\007Removed\020\005" + - "B\035\n\031akka.cluster.protobuf.msgH\001" + "\022\014\n\010WeaklyUp\020\006B\035\n\031akka.cluster.protobuf.", + "msgH\001" }; akka.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner assigner = new akka.protobuf.Descriptors.FileDescriptor.InternalDescriptorAssigner() { diff --git a/akka-cluster/src/main/protobuf/ClusterMessages.proto b/akka-cluster/src/main/protobuf/ClusterMessages.proto index 467b8ce914..8d84285caf 100644 --- a/akka-cluster/src/main/protobuf/ClusterMessages.proto +++ b/akka-cluster/src/main/protobuf/ClusterMessages.proto @@ -157,6 +157,7 @@ enum MemberStatus { Exiting = 3; Down = 4; Removed = 5; + WeaklyUp = 6; } /** diff --git a/akka-cluster/src/main/resources/reference.conf b/akka-cluster/src/main/resources/reference.conf index b6f4969801..d4ea40af7a 100644 --- a/akka-cluster/src/main/resources/reference.conf +++ b/akka-cluster/src/main/resources/reference.conf @@ -39,7 +39,14 @@ akka { # handling network partitions. # Disable with "off" or specify a duration to enable. down-removal-margin = off - + + # By default, the leader will not move 'Joining' members to 'Up' during a network + # split. This feature allows the leader to accept 'Joining' members to be 'WeaklyUp' + # so they become part of the cluster even during a network split. The leader will + # move 'WeaklyUp' members to 'Up' status once convergence has been reached. This + # feature must be off if some members are running Akka 2.3.X. + allow-weakly-up-members = off + # The roles of this member. List of strings, e.g. roles = ["A", "B"]. # The roles are part of the membership information and can be used by # routers or other services to distribute work to certain member types, diff --git a/akka-cluster/src/main/scala/akka/cluster/ClusterDaemon.scala b/akka-cluster/src/main/scala/akka/cluster/ClusterDaemon.scala index 01a3cc2bea..7a09543853 100644 --- a/akka-cluster/src/main/scala/akka/cluster/ClusterDaemon.scala +++ b/akka-cluster/src/main/scala/akka/cluster/ClusterDaemon.scala @@ -829,6 +829,9 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with leaderActionCounter = 0 leaderActionsOnConvergence() } else { + if (cluster.settings.AllowWeaklyUpMembers) + moveJoiningToWeaklyUp() + leaderActionCounter += 1 if (leaderActionCounter == firstNotice || leaderActionCounter % periodicNotice == 0) logInfo("Leader can currently not perform its duties, reachability status: [{}], member status: [{}]", @@ -859,6 +862,12 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with } } + def isMinNrOfMembersFulfilled: Boolean = { + latestGossip.members.size >= MinNrOfMembers && MinNrOfMembersOfRole.forall { + case (role, threshold) ⇒ latestGossip.members.count(_.hasRole(role)) >= threshold + } + } + /** * Leader actions are as follows: * 1. Move JOINING => UP -- When a node joins the cluster @@ -878,12 +887,8 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with val localOverview = localGossip.overview val localSeen = localOverview.seen - def enoughMembers: Boolean = { - localMembers.size >= MinNrOfMembers && MinNrOfMembersOfRole.forall { - case (role, threshold) ⇒ localMembers.count(_.hasRole(role)) >= threshold - } - } - def isJoiningToUp(m: Member): Boolean = m.status == Joining && enoughMembers + val enoughMembers: Boolean = isMinNrOfMembersFulfilled + def isJoiningToUp(m: Member): Boolean = (m.status == Joining || m.status == WeaklyUp) && enoughMembers val removedUnreachable = for { node ← localOverview.reachability.allUnreachableOrTerminated @@ -967,6 +972,33 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with } } + def moveJoiningToWeaklyUp(): Unit = { + val localGossip = latestGossip + val localMembers = localGossip.members + + val enoughMembers: Boolean = isMinNrOfMembersFulfilled + def isJoiningToWeaklyUp(m: Member): Boolean = + m.status == Joining && enoughMembers && latestGossip.reachabilityExcludingDownedObservers.isReachable(m.uniqueAddress) + val changedMembers = localMembers.collect { + case m if isJoiningToWeaklyUp(m) ⇒ m.copy(status = WeaklyUp) + } + + if (changedMembers.nonEmpty) { + // replace changed members + val newMembers = changedMembers ++ localMembers + val newGossip = localGossip.copy(members = newMembers) + updateLatestGossip(newGossip) + + // log status changes + changedMembers foreach { m ⇒ + logInfo("Leader is moving node [{}] to [{}]", m.address, m.status) + } + + publish(latestGossip) + } + + } + /** * Reaps the unreachable members according to the failure detector's verdict. */ diff --git a/akka-cluster/src/main/scala/akka/cluster/ClusterEvent.scala b/akka-cluster/src/main/scala/akka/cluster/ClusterEvent.scala index 8332ae8b86..be95f74148 100644 --- a/akka-cluster/src/main/scala/akka/cluster/ClusterEvent.scala +++ b/akka-cluster/src/main/scala/akka/cluster/ClusterEvent.scala @@ -121,6 +121,16 @@ object ClusterEvent { def member: Member } + /** + * Member status changed to WeaklyUp. + * A joining member can be moved to `WeaklyUp` if convergence + * cannot be reached, i.e. there are unreachable nodes. + * It will be moved to `Up` when convergence is reached. + */ + final case class MemberWeaklyUp(member: Member) extends MemberEvent { + if (member.status != WeaklyUp) throw new IllegalArgumentException("Expected WeaklyUp status, got: " + member) + } + /** * Member status changed to Up. */ @@ -268,8 +278,9 @@ object ClusterEvent { case (_, newMember :: oldMember :: Nil) if newMember.status != oldMember.status ⇒ newMember } val memberEvents = (newMembers ++ changedMembers) collect { - case m if m.status == Up ⇒ MemberUp(m) - case m if m.status == Exiting ⇒ MemberExited(m) + case m if m.status == WeaklyUp ⇒ MemberWeaklyUp(m) + case m if m.status == Up ⇒ MemberUp(m) + case m if m.status == Exiting ⇒ MemberExited(m) // no events for other transitions } diff --git a/akka-cluster/src/main/scala/akka/cluster/ClusterHeartbeat.scala b/akka-cluster/src/main/scala/akka/cluster/ClusterHeartbeat.scala index a5ac2e2022..a6e2f41d80 100644 --- a/akka-cluster/src/main/scala/akka/cluster/ClusterHeartbeat.scala +++ b/akka-cluster/src/main/scala/akka/cluster/ClusterHeartbeat.scala @@ -111,6 +111,7 @@ private[cluster] final class ClusterHeartbeatSender extends Actor with ActorLogg case HeartbeatTick ⇒ heartbeat() case HeartbeatRsp(from) ⇒ heartbeatRsp(from) case MemberUp(m) ⇒ addMember(m) + case MemberWeaklyUp(m) ⇒ addMember(m) case MemberRemoved(m, _) ⇒ removeMember(m) case UnreachableMember(m) ⇒ unreachableMember(m) case ReachableMember(m) ⇒ reachableMember(m) @@ -120,7 +121,7 @@ private[cluster] final class ClusterHeartbeatSender extends Actor with ActorLogg def init(snapshot: CurrentClusterState): Unit = { val nodes: Set[UniqueAddress] = snapshot.members.collect { - case m if m.status == MemberStatus.Up ⇒ m.uniqueAddress + case m if m.status == MemberStatus.Up || m.status == MemberStatus.WeaklyUp ⇒ m.uniqueAddress }(collection.breakOut) val unreachable: Set[UniqueAddress] = snapshot.unreachable.map(_.uniqueAddress) state = state.init(nodes, unreachable) diff --git a/akka-cluster/src/main/scala/akka/cluster/ClusterRemoteWatcher.scala b/akka-cluster/src/main/scala/akka/cluster/ClusterRemoteWatcher.scala index 8119bde413..17ed4088c9 100644 --- a/akka-cluster/src/main/scala/akka/cluster/ClusterRemoteWatcher.scala +++ b/akka-cluster/src/main/scala/akka/cluster/ClusterRemoteWatcher.scala @@ -9,6 +9,7 @@ import akka.cluster.ClusterEvent.CurrentClusterState import akka.cluster.ClusterEvent.MemberEvent import akka.cluster.ClusterEvent.MemberUp import akka.cluster.ClusterEvent.MemberRemoved +import akka.cluster.ClusterEvent.MemberWeaklyUp import akka.remote.FailureDetectorRegistry import akka.remote.RemoteWatcher @@ -72,23 +73,28 @@ private[cluster] class ClusterRemoteWatcher( clusterNodes = state.members.collect { case m if m.address != selfAddress ⇒ m.address } clusterNodes foreach takeOverResponsibility unreachable --= clusterNodes - case MemberUp(m) ⇒ - if (m.address != selfAddress) { - clusterNodes += m.address - takeOverResponsibility(m.address) - unreachable -= m.address - } - case MemberRemoved(m, previousStatus) ⇒ - if (m.address != selfAddress) { - clusterNodes -= m.address - if (previousStatus == MemberStatus.Down) { - quarantine(m.address, Some(m.uniqueAddress.uid)) - } - publishAddressTerminated(m.address) - } - case _: MemberEvent ⇒ // not interesting + case MemberUp(m) ⇒ memberUp(m) + case MemberWeaklyUp(m) ⇒ memberUp(m) + case MemberRemoved(m, previousStatus) ⇒ memberRemoved(m, previousStatus) + case _: MemberEvent ⇒ // not interesting } + def memberUp(m: Member): Unit = + if (m.address != selfAddress) { + clusterNodes += m.address + takeOverResponsibility(m.address) + unreachable -= m.address + } + + def memberRemoved(m: Member, previousStatus: MemberStatus): Unit = + if (m.address != selfAddress) { + clusterNodes -= m.address + if (previousStatus == MemberStatus.Down) { + quarantine(m.address, Some(m.uniqueAddress.uid)) + } + publishAddressTerminated(m.address) + } + override def watchNode(watchee: InternalActorRef) = if (!clusterNodes(watchee.path.address)) super.watchNode(watchee) @@ -103,4 +109,4 @@ private[cluster] class ClusterRemoteWatcher( unwatchNode(address) } -} \ No newline at end of file +} diff --git a/akka-cluster/src/main/scala/akka/cluster/ClusterSettings.scala b/akka-cluster/src/main/scala/akka/cluster/ClusterSettings.scala index cb66e13fe1..df783c5b2b 100644 --- a/akka-cluster/src/main/scala/akka/cluster/ClusterSettings.scala +++ b/akka-cluster/src/main/scala/akka/cluster/ClusterSettings.scala @@ -75,6 +75,8 @@ final class ClusterSettings(val config: Config, val systemName: String) { } } + val AllowWeaklyUpMembers = cc.getBoolean("allow-weakly-up-members") + val Roles: Set[String] = immutableSeq(cc.getStringList("roles")).toSet val MinNrOfMembers: Int = { cc.getInt("min-nr-of-members") diff --git a/akka-cluster/src/main/scala/akka/cluster/Member.scala b/akka-cluster/src/main/scala/akka/cluster/Member.scala index 1b33eeaa54..d1201c8120 100644 --- a/akka-cluster/src/main/scala/akka/cluster/Member.scala +++ b/akka-cluster/src/main/scala/akka/cluster/Member.scala @@ -108,6 +108,8 @@ object Member { case (_, Exiting) ⇒ true case (Joining, _) ⇒ false case (_, Joining) ⇒ true + case (WeaklyUp, _) ⇒ false + case (_, WeaklyUp) ⇒ true case _ ⇒ ordering.compare(a, b) <= 0 } } @@ -140,17 +142,19 @@ object Member { * Picks the Member with the highest "priority" MemberStatus. */ def highestPriorityOf(m1: Member, m2: Member): Member = (m1.status, m2.status) match { - case (Removed, _) ⇒ m1 - case (_, Removed) ⇒ m2 - case (Down, _) ⇒ m1 - case (_, Down) ⇒ m2 - case (Exiting, _) ⇒ m1 - case (_, Exiting) ⇒ m2 - case (Leaving, _) ⇒ m1 - case (_, Leaving) ⇒ m2 - case (Joining, _) ⇒ m2 - case (_, Joining) ⇒ m1 - case (Up, Up) ⇒ m1 + case (Removed, _) ⇒ m1 + case (_, Removed) ⇒ m2 + case (Down, _) ⇒ m1 + case (_, Down) ⇒ m2 + case (Exiting, _) ⇒ m1 + case (_, Exiting) ⇒ m2 + case (Leaving, _) ⇒ m1 + case (_, Leaving) ⇒ m2 + case (Joining, _) ⇒ m2 + case (_, Joining) ⇒ m1 + case (WeaklyUp, _) ⇒ m2 + case (_, WeaklyUp) ⇒ m1 + case (Up, Up) ⇒ m1 } } @@ -164,6 +168,7 @@ abstract class MemberStatus object MemberStatus { @SerialVersionUID(1L) case object Joining extends MemberStatus + @SerialVersionUID(1L) case object WeaklyUp extends MemberStatus @SerialVersionUID(1L) case object Up extends MemberStatus @SerialVersionUID(1L) case object Leaving extends MemberStatus @SerialVersionUID(1L) case object Exiting extends MemberStatus @@ -205,7 +210,8 @@ object MemberStatus { */ private[cluster] val allowedTransitions: Map[MemberStatus, Set[MemberStatus]] = Map( - Joining -> Set(Up, Down, Removed), + Joining -> Set(WeaklyUp, Up, Down, Removed), + WeaklyUp -> Set(Up, Down, Removed), Up -> Set(Leaving, Down, Removed), Leaving -> Set(Exiting, Down, Removed), Down -> Set(Removed), diff --git a/akka-cluster/src/main/scala/akka/cluster/protobuf/ClusterMessageSerializer.scala b/akka-cluster/src/main/scala/akka/cluster/protobuf/ClusterMessageSerializer.scala index 030eba0c32..e0e7e745bb 100644 --- a/akka-cluster/src/main/scala/akka/cluster/protobuf/ClusterMessageSerializer.scala +++ b/akka-cluster/src/main/scala/akka/cluster/protobuf/ClusterMessageSerializer.scala @@ -168,7 +168,8 @@ class ClusterMessageSerializer(val system: ExtendedActorSystem) extends BaseSeri MemberStatus.Leaving -> cm.MemberStatus.Leaving_VALUE, MemberStatus.Exiting -> cm.MemberStatus.Exiting_VALUE, MemberStatus.Down -> cm.MemberStatus.Down_VALUE, - MemberStatus.Removed -> cm.MemberStatus.Removed_VALUE) + MemberStatus.Removed -> cm.MemberStatus.Removed_VALUE, + MemberStatus.WeaklyUp -> cm.MemberStatus.WeaklyUp_VALUE) private val memberStatusFromInt = memberStatusToInt.map { case (a, b) ⇒ (b, a) } diff --git a/akka-cluster/src/main/scala/akka/cluster/routing/ClusterRouterConfig.scala b/akka-cluster/src/main/scala/akka/cluster/routing/ClusterRouterConfig.scala index a0a9d26125..f691325ec9 100644 --- a/akka-cluster/src/main/scala/akka/cluster/routing/ClusterRouterConfig.scala +++ b/akka-cluster/src/main/scala/akka/cluster/routing/ClusterRouterConfig.scala @@ -382,7 +382,7 @@ private[akka] trait ClusterRouterActor { this: RouterActor ⇒ } def isAvailable(m: Member): Boolean = - m.status == MemberStatus.Up && + (m.status == MemberStatus.Up || m.status == MemberStatus.WeaklyUp) && satisfiesRole(m.roles) && (settings.allowLocalRoutees || m.address != cluster.selfAddress) diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/MemberWeaklyUpSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/MemberWeaklyUpSpec.scala new file mode 100644 index 0000000000..d97a240bb3 --- /dev/null +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/MemberWeaklyUpSpec.scala @@ -0,0 +1,119 @@ +/** + * Copyright (C) 2009-2014 Typesafe Inc. + */ +package akka.cluster + +import scala.language.postfixOps +import scala.concurrent.duration._ +import akka.remote.testkit.MultiNodeConfig +import akka.remote.testkit.MultiNodeSpec +import akka.remote.transport.ThrottlerTransportAdapter.Direction +import akka.testkit._ +import com.typesafe.config.ConfigFactory +import akka.cluster.MemberStatus.WeaklyUp + +object MemberWeaklyUpSpec extends MultiNodeConfig { + val first = role("first") + val second = role("second") + val third = role("third") + val fourth = role("fourth") + val fifth = role("fifth") + + commonConfig(debugConfig(on = false). + withFallback(ConfigFactory.parseString(""" + akka.remote.retry-gate-closed-for = 3 s + akka.cluster.allow-weakly-up-members = on + """)). + withFallback(MultiNodeClusterSpec.clusterConfig)) + + testTransport(on = true) +} + +class MemberWeaklyUpMultiJvmNode1 extends MemberWeaklyUpSpec +class MemberWeaklyUpMultiJvmNode2 extends MemberWeaklyUpSpec +class MemberWeaklyUpMultiJvmNode3 extends MemberWeaklyUpSpec +class MemberWeaklyUpMultiJvmNode4 extends MemberWeaklyUpSpec +class MemberWeaklyUpMultiJvmNode5 extends MemberWeaklyUpSpec + +abstract class MemberWeaklyUpSpec + extends MultiNodeSpec(MemberWeaklyUpSpec) + with MultiNodeClusterSpec { + + import MemberWeaklyUpSpec._ + + muteMarkingAsUnreachable() + + val side1 = Vector(first, second) + val side2 = Vector(third, fourth, fifth) + + "A cluster of 3 members" must { + + "reach initial convergence" taggedAs LongRunningTest in { + awaitClusterUp(first, third, fourth) + + enterBarrier("after-1") + } + + "detect network partition and mark nodes on other side as unreachable" taggedAs LongRunningTest in within(20 seconds) { + runOn(first) { + // split the cluster in two parts (first, second) / (third, fourth, fifth) + for (role1 ← side1; role2 ← side2) { + testConductor.blackhole(role1, role2, Direction.Both).await + } + } + enterBarrier("after-split") + + runOn(first) { + awaitAssert(clusterView.unreachableMembers.map(_.address) should be(Set(address(third), address(fourth)))) + } + + runOn(third, fourth) { + awaitAssert(clusterView.unreachableMembers.map(_.address) should be(Set(address(first)))) + } + + enterBarrier("after-2") + } + + "accept joining on each side and set status to WeaklyUp" taggedAs LongRunningTest in within(20 seconds) { + runOn(second) { + Cluster(system).join(first) + } + runOn(fifth) { + Cluster(system).join(fourth) + } + enterBarrier("joined") + + runOn(side1: _*) { + awaitAssert { + clusterView.members.size should be(4) + clusterView.members.exists { m ⇒ m.address == address(second) && m.status == WeaklyUp } should be(true) + } + } + + runOn(side2: _*) { + awaitAssert { + clusterView.members.size should be(4) + clusterView.members.exists { m ⇒ m.address == address(fifth) && m.status == WeaklyUp } should be(true) + } + } + + enterBarrier("after-3") + } + + "change status to Up after healed network partition" taggedAs LongRunningTest in within(20 seconds) { + runOn(first) { + for (role1 ← side1; role2 ← side2) { + testConductor.passThrough(role1, role2, Direction.Both).await + } + } + enterBarrier("after-passThrough") + + awaitAllReachable() + awaitMembersUp(5) + + enterBarrier("after-4") + } + + } + +} diff --git a/akka-cluster/src/multi-jvm/scala/akka/cluster/MinMembersBeforeUpSpec.scala b/akka-cluster/src/multi-jvm/scala/akka/cluster/MinMembersBeforeUpSpec.scala index e05e44d834..443748e14c 100644 --- a/akka-cluster/src/multi-jvm/scala/akka/cluster/MinMembersBeforeUpSpec.scala +++ b/akka-cluster/src/multi-jvm/scala/akka/cluster/MinMembersBeforeUpSpec.scala @@ -26,6 +26,17 @@ object MinMembersBeforeUpMultiJvmSpec extends MultiNodeConfig { withFallback(MultiNodeClusterSpec.clusterConfigWithFailureDetectorPuppet)) } +object MinMembersBeforeUpWithWeaklyUpMultiJvmSpec extends MultiNodeConfig { + val first = role("first") + val second = role("second") + val third = role("third") + + commonConfig(debugConfig(on = false).withFallback(ConfigFactory.parseString(""" + akka.cluster.min-nr-of-members = 3 + akka.cluster.allow-weakly-up-members = on""")). + withFallback(MultiNodeClusterSpec.clusterConfigWithFailureDetectorPuppet)) +} + object MinMembersOfRoleBeforeUpMultiJvmSpec extends MultiNodeConfig { val first = role("first") val second = role("second") @@ -46,6 +57,10 @@ class MinMembersBeforeUpMultiJvmNode1 extends MinMembersBeforeUpSpec class MinMembersBeforeUpMultiJvmNode2 extends MinMembersBeforeUpSpec class MinMembersBeforeUpMultiJvmNode3 extends MinMembersBeforeUpSpec +class MinMembersBeforeUpWithWeaklyUpMultiJvmNode1 extends MinMembersBeforeUpSpec +class MinMembersBeforeUpWithWeaklyUpMultiJvmNode2 extends MinMembersBeforeUpSpec +class MinMembersBeforeUpWithWeaklyUpMultiJvmNode3 extends MinMembersBeforeUpSpec + class MinMembersOfRoleBeforeUpMultiJvmNode1 extends MinMembersOfRoleBeforeUpSpec class MinMembersOfRoleBeforeUpMultiJvmNode2 extends MinMembersOfRoleBeforeUpSpec class MinMembersOfRoleBeforeUpMultiJvmNode3 extends MinMembersOfRoleBeforeUpSpec @@ -63,6 +78,19 @@ abstract class MinMembersBeforeUpSpec extends MinMembersBeforeUpBase(MinMembersB } } +abstract class MinMembersBeforeUpWithWeaklyUpSpec extends MinMembersBeforeUpBase(MinMembersBeforeUpMultiJvmSpec) { + + override def first: RoleName = MinMembersBeforeUpWithWeaklyUpMultiJvmSpec.first + override def second: RoleName = MinMembersBeforeUpWithWeaklyUpMultiJvmSpec.second + override def third: RoleName = MinMembersBeforeUpWithWeaklyUpMultiJvmSpec.third + + "Cluster leader" must { + "wait with moving members to UP until minimum number of members have joined with weakly up enabled" taggedAs LongRunningTest in { + testWaitMovingMembersToUp() + } + } +} + abstract class MinMembersOfRoleBeforeUpSpec extends MinMembersBeforeUpBase(MinMembersOfRoleBeforeUpMultiJvmSpec) { override def first: RoleName = MinMembersOfRoleBeforeUpMultiJvmSpec.first diff --git a/akka-docs/rst/common/cluster.rst b/akka-docs/rst/common/cluster.rst index ff6c3ecbf9..1ad18cb3dd 100644 --- a/akka-docs/rst/common/cluster.rst +++ b/akka-docs/rst/common/cluster.rst @@ -273,12 +273,22 @@ leader, also *auto-down* a node after a configured time of unreachability.. follows from the fact that the ``unreachable`` node will likely see the rest of the cluster as ``unreachable``, become its own leader and form its own cluster. +As mentioned before, if a node is ``unreachable`` then gossip convergence is not +possible and therefore any ``leader`` actions are also not possible. By enabling +``akka.cluster.allow-weakly-up-members`` it is possible to let new joining nodes be +promoted while convergence is not yet reached. These ``Joining`` nodes will be +promoted as ``WeaklyUp``. Once gossip convergence is reached, the leader will move +``WeaklyUp`` members to ``Up``. -State Diagram for the Member States -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +State Diagram for the Member States (``akka.cluster.allow-weakly-up-members=off``) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. image:: ../images/member-states.png +State Diagram for the Member States (``akka.cluster.allow-weakly-up-members=on``) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +.. image:: ../images/member-states-weakly-up.png Member States ^^^^^^^^^^^^^ @@ -286,6 +296,9 @@ Member States - **joining** transient state when joining a cluster +- **weakly up** + transient state while network split (only if ``akka.cluster.allow-weakly-up-members=on``) + - **up** normal operating state diff --git a/akka-docs/rst/images/member-states-weakly-up.png b/akka-docs/rst/images/member-states-weakly-up.png new file mode 100644 index 0000000000000000000000000000000000000000..8993e9acc17bc2ef9807ca6181b517b4849c2529 GIT binary patch literal 43785 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 literal 0 HcmV?d00001 diff --git a/akka-docs/rst/java/cluster-usage.rst b/akka-docs/rst/java/cluster-usage.rst index 3ba75351fd..5033bf1917 100644 --- a/akka-docs/rst/java/cluster-usage.rst +++ b/akka-docs/rst/java/cluster-usage.rst @@ -169,6 +169,27 @@ leaving member will be shutdown after the leader has changed status of the membe automatically, but in case of network failures during this process it might still be necessary to set the node’s status to ``Down`` in order to complete the removal. +WeaklyUp Members +^^^^^^^^^^^^^^^^ + +If a node is ``unreachable`` then gossip convergence is not possible and therefore any +``leader`` actions are also not possible. However, we still might want new nodes to join +the cluster in this scenario. + +With a configuration option you can allow this behavior:: + + akka.cluster.allow-weakly-up-members = on + +When ``allow-weakly-up-members`` is enabled and there is no gossip convergence, +``Joining`` members will be promoted to ``WeaklyUp`` and they will become part of the +cluster. Once gossip convergence is reached, the leader will move ``WeaklyUp`` +members to ``Up``. + +.. warning:: + + This feature is only available from Akka 2.4.0 and cannot be used if some of your + cluster members are running an older version of Akka. + .. _cluster_subscriber_java: Subscribe to Cluster Events diff --git a/akka-docs/rst/scala/cluster-usage.rst b/akka-docs/rst/scala/cluster-usage.rst index de48ed689b..5adcc4c531 100644 --- a/akka-docs/rst/scala/cluster-usage.rst +++ b/akka-docs/rst/scala/cluster-usage.rst @@ -163,6 +163,27 @@ leaving member will be shutdown after the leader has changed status of the membe automatically, but in case of network failures during this process it might still be necessary to set the node’s status to ``Down`` in order to complete the removal. +WeaklyUp Members +^^^^^^^^^^^^^^^^ + +If a node is ``unreachable`` then gossip convergence is not possible and therefore any +``leader`` actions are also not possible. However, we still might want new nodes to join +the cluster in this scenario. + +With a configuration option you can allow this behavior:: + + akka.cluster.allow-weakly-up-members = on + +When ``allow-weakly-up-members`` is enabled and there is no gossip convergence, +``Joining`` members will be promoted to ``WeaklyUp`` and they will become part of the +cluster. Once gossip convergence is reached, the leader will move ``WeaklyUp`` +members to ``Up``. + +.. warning:: + + This feature is only available from Akka 2.4.0 and cannot be used if some of your + cluster members are running an older version of Akka. + .. _cluster_subscriber_scala: Subscribe to Cluster Events diff --git a/project/MiMa.scala b/project/MiMa.scala index cf0304fc43..4e1c820d4c 100644 --- a/project/MiMa.scala +++ b/project/MiMa.scala @@ -566,7 +566,10 @@ object MiMa extends AutoPlugin { FilterAnyProblemStartingWith("akka.remote.serialization.DaemonMsgCreateSerializer"), FilterAnyProblemStartingWith("akka.remote.testconductor.TestConductorProtocol"), FilterAnyProblemStartingWith("akka.cluster.protobuf.msg.ClusterMessages"), - FilterAnyProblemStartingWith("akka.cluster.protobuf.ClusterMessageSerializer") + FilterAnyProblemStartingWith("akka.cluster.protobuf.ClusterMessageSerializer"), + + // #13584 change in internal actor + ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.cluster.ClusterCoreDaemon.akka$cluster$ClusterCoreDaemon$$isJoiningToUp$1") ) }