=clu #13875 Exclude unreachability observations from downed

* Skip observations from downed node (quarantined is marked down immediately)
  in convergence check
* Skip observations from downed node when picking "reachable" targets for gossip.
* This also means that we must accept gossip with own node marked as unreachable,
  but that should not be spread to the external membership events.
This commit is contained in:
Patrik Nordwall 2015-01-30 14:30:16 +01:00
parent 8e6d81242f
commit 71ccb4c21b
8 changed files with 147 additions and 42 deletions

View file

@ -236,7 +236,8 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
var gossipStats = GossipStats() var gossipStats = GossipStats()
var seedNodeProcess: Option[ActorRef] = None var seedNodeProcess: Option[ActorRef] = None
var seedNodeProcessCounter = 0 // for unique names var seedNodeProcessCounter = 0 // for unique names
var leaderActionCounter = 0
/** /**
* Looks up and returns the remote cluster command connection for the specific address. * Looks up and returns the remote cluster command connection for the specific address.
@ -608,9 +609,6 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
} else if (envelope.to != selfUniqueAddress) { } else if (envelope.to != selfUniqueAddress) {
logInfo("Ignoring received gossip intended for someone else, from [{}] to [{}]", from.address, envelope.to) logInfo("Ignoring received gossip intended for someone else, from [{}] to [{}]", from.address, envelope.to)
Ignored Ignored
} else if (!remoteGossip.overview.reachability.isReachable(selfUniqueAddress)) {
logInfo("Ignoring received gossip with myself as unreachable, from [{}]", from.address)
Ignored
} else if (!localGossip.overview.reachability.isReachable(selfUniqueAddress, from)) { } else if (!localGossip.overview.reachability.isReachable(selfUniqueAddress, from)) {
logInfo("Ignoring received gossip from unreachable [{}] ", from) logInfo("Ignoring received gossip from unreachable [{}] ", from)
Ignored Ignored
@ -758,8 +756,21 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
def leaderActions(): Unit = def leaderActions(): Unit =
if (latestGossip.isLeader(selfUniqueAddress)) { if (latestGossip.isLeader(selfUniqueAddress)) {
// only run the leader actions if we are the LEADER // only run the leader actions if we are the LEADER
if (latestGossip.convergence) val firstNotice = 20
val periodicNotice = 60
if (latestGossip.convergence(selfUniqueAddress)) {
if (leaderActionCounter >= firstNotice)
logInfo("Leader can perform its duties again")
leaderActionCounter = 0
leaderActionsOnConvergence() leaderActionsOnConvergence()
} else {
leaderActionCounter += 1
if (leaderActionCounter == firstNotice || leaderActionCounter % periodicNotice == 0)
logInfo("Leader can currently not perform its duties, reachability status: [{}], member status: [{}]",
latestGossip.reachabilityExcludingDownedObservers,
latestGossip.members.map(m
s"${m.address} ${m.status} seen=${latestGossip.seenByNode(m.uniqueAddress)}").mkString(", "))
}
} }
/** /**
@ -956,7 +967,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
def validNodeForGossip(node: UniqueAddress): Boolean = def validNodeForGossip(node: UniqueAddress): Boolean =
(node != selfUniqueAddress && latestGossip.hasMember(node) && (node != selfUniqueAddress && latestGossip.hasMember(node) &&
latestGossip.overview.reachability.isReachable(node)) latestGossip.reachabilityExcludingDownedObservers.isReachable(node))
def updateLatestGossip(newGossip: Gossip): Unit = { def updateLatestGossip(newGossip: Gossip): Unit = {
// Updating the vclock version for the changes // Updating the vclock version for the changes

View file

@ -222,12 +222,12 @@ object ClusterEvent {
/** /**
* INTERNAL API * INTERNAL API
*/ */
private[cluster] def diffUnreachable(oldGossip: Gossip, newGossip: Gossip): immutable.Seq[UnreachableMember] = private[cluster] def diffUnreachable(oldGossip: Gossip, newGossip: Gossip, selfUniqueAddress: UniqueAddress): immutable.Seq[UnreachableMember] =
if (newGossip eq oldGossip) Nil if (newGossip eq oldGossip) Nil
else { else {
val oldUnreachableNodes = oldGossip.overview.reachability.allUnreachableOrTerminated val oldUnreachableNodes = oldGossip.overview.reachability.allUnreachableOrTerminated
(newGossip.overview.reachability.allUnreachableOrTerminated.collect { (newGossip.overview.reachability.allUnreachableOrTerminated.collect {
case node if !oldUnreachableNodes.contains(node) case node if !oldUnreachableNodes.contains(node) && node != selfUniqueAddress
UnreachableMember(newGossip.member(node)) UnreachableMember(newGossip.member(node))
})(collection.breakOut) })(collection.breakOut)
} }
@ -235,11 +235,11 @@ object ClusterEvent {
/** /**
* INTERNAL API * INTERNAL API
*/ */
private[cluster] def diffReachable(oldGossip: Gossip, newGossip: Gossip): immutable.Seq[ReachableMember] = private[cluster] def diffReachable(oldGossip: Gossip, newGossip: Gossip, selfUniqueAddress: UniqueAddress): immutable.Seq[ReachableMember] =
if (newGossip eq oldGossip) Nil if (newGossip eq oldGossip) Nil
else { else {
(oldGossip.overview.reachability.allUnreachable.collect { (oldGossip.overview.reachability.allUnreachable.collect {
case node if newGossip.hasMember(node) && newGossip.overview.reachability.isReachable(node) case node if newGossip.hasMember(node) && newGossip.overview.reachability.isReachable(node) && node != selfUniqueAddress
ReachableMember(newGossip.member(node)) ReachableMember(newGossip.member(node))
})(collection.breakOut) })(collection.breakOut)
@ -291,12 +291,12 @@ object ClusterEvent {
/** /**
* INTERNAL API * INTERNAL API
*/ */
private[cluster] def diffSeen(oldGossip: Gossip, newGossip: Gossip): immutable.Seq[SeenChanged] = private[cluster] def diffSeen(oldGossip: Gossip, newGossip: Gossip, selfUniqueAddress: UniqueAddress): immutable.Seq[SeenChanged] =
if (newGossip eq oldGossip) Nil if (newGossip eq oldGossip) Nil
else { else {
val newConvergence = newGossip.convergence val newConvergence = newGossip.convergence(selfUniqueAddress)
val newSeenBy = newGossip.seenBy val newSeenBy = newGossip.seenBy
if (newConvergence != oldGossip.convergence || newSeenBy != oldGossip.seenBy) if (newConvergence != oldGossip.convergence(selfUniqueAddress) || newSeenBy != oldGossip.seenBy)
List(SeenChanged(newConvergence, newSeenBy.map(_.address))) List(SeenChanged(newConvergence, newSeenBy.map(_.address)))
else Nil else Nil
} }
@ -319,6 +319,7 @@ private[cluster] final class ClusterDomainEventPublisher extends Actor with Acto
with RequiresMessageQueue[UnboundedMessageQueueSemantics] { with RequiresMessageQueue[UnboundedMessageQueueSemantics] {
import InternalClusterAction._ import InternalClusterAction._
val selfUniqueAddress = Cluster(context.system).selfUniqueAddress
var latestGossip: Gossip = Gossip.empty var latestGossip: Gossip = Gossip.empty
override def preRestart(reason: Throwable, message: Option[Any]) { override def preRestart(reason: Throwable, message: Option[Any]) {
@ -346,9 +347,12 @@ private[cluster] final class ClusterDomainEventPublisher extends Actor with Acto
* to mimic what you would have seen if you were listening to the events. * to mimic what you would have seen if you were listening to the events.
*/ */
def sendCurrentClusterState(receiver: ActorRef): Unit = { def sendCurrentClusterState(receiver: ActorRef): Unit = {
val unreachable: Set[Member] = latestGossip.overview.reachability.allUnreachableOrTerminated.collect {
case node if node != selfUniqueAddress latestGossip.member(node)
}
val state = CurrentClusterState( val state = CurrentClusterState(
members = latestGossip.members, members = latestGossip.members,
unreachable = latestGossip.overview.reachability.allUnreachableOrTerminated map latestGossip.member, unreachable = unreachable,
seenBy = latestGossip.seenBy.map(_.address), seenBy = latestGossip.seenBy.map(_.address),
leader = latestGossip.leader.map(_.address), leader = latestGossip.leader.map(_.address),
roleLeaderMap = latestGossip.allRoles.map(r r -> latestGossip.roleLeader(r).map(_.address))(collection.breakOut)) roleLeaderMap = latestGossip.allRoles.map(r r -> latestGossip.roleLeader(r).map(_.address))(collection.breakOut))
@ -384,12 +388,12 @@ private[cluster] final class ClusterDomainEventPublisher extends Actor with Acto
def publishDiff(oldGossip: Gossip, newGossip: Gossip, pub: AnyRef Unit): Unit = { def publishDiff(oldGossip: Gossip, newGossip: Gossip, pub: AnyRef Unit): Unit = {
diffMemberEvents(oldGossip, newGossip) foreach pub diffMemberEvents(oldGossip, newGossip) foreach pub
diffUnreachable(oldGossip, newGossip) foreach pub diffUnreachable(oldGossip, newGossip, selfUniqueAddress) foreach pub
diffReachable(oldGossip, newGossip) foreach pub diffReachable(oldGossip, newGossip, selfUniqueAddress) foreach pub
diffLeader(oldGossip, newGossip) foreach pub diffLeader(oldGossip, newGossip) foreach pub
diffRolesLeader(oldGossip, newGossip) foreach pub diffRolesLeader(oldGossip, newGossip) foreach pub
// publish internal SeenState for testing purposes // publish internal SeenState for testing purposes
diffSeen(oldGossip, newGossip) foreach pub diffSeen(oldGossip, newGossip, selfUniqueAddress) foreach pub
diffReachability(oldGossip, newGossip) foreach pub diffReachability(oldGossip, newGossip) foreach pub
} }

View file

@ -20,7 +20,7 @@ private[cluster] object Gossip {
if (members.isEmpty) empty else empty.copy(members = members) if (members.isEmpty) empty else empty.copy(members = members)
private val leaderMemberStatus = Set[MemberStatus](Up, Leaving) private val leaderMemberStatus = Set[MemberStatus](Up, Leaving)
private val convergenceMemberStatus = Set[MemberStatus](Up, Leaving) val convergenceMemberStatus = Set[MemberStatus](Up, Leaving) // FIXME private
val convergenceSkipUnreachableWithMemberStatus = Set[MemberStatus](Down, Exiting) val convergenceSkipUnreachableWithMemberStatus = Set[MemberStatus](Down, Exiting)
val removeUnreachableWithMemberStatus = Set[MemberStatus](Down, Exiting) val removeUnreachableWithMemberStatus = Set[MemberStatus](Down, Exiting)
@ -159,19 +159,26 @@ private[cluster] final case class Gossip(
* *
* @return true if convergence have been reached and false if not * @return true if convergence have been reached and false if not
*/ */
def convergence: Boolean = { def convergence(selfUniqueAddress: UniqueAddress): Boolean = {
// First check that: // First check that:
// 1. we don't have any members that are unreachable, or // 1. we don't have any members that are unreachable, excluding observations from members
// that have status DOWN, or
// 2. all unreachable members in the set have status DOWN or EXITING // 2. all unreachable members in the set have status DOWN or EXITING
// Else we can't continue to check for convergence // Else we can't continue to check for convergence
// When that is done we check that all members with a convergence // When that is done we check that all members with a convergence
// status is in the seen table and has the latest vector clock // status is in the seen table, i.e. has seen this version
// version val unreachable = reachabilityExcludingDownedObservers.allUnreachableOrTerminated.collect {
val unreachable = overview.reachability.allUnreachableOrTerminated map member case node if (node != selfUniqueAddress) member(node)
}
unreachable.forall(m Gossip.convergenceSkipUnreachableWithMemberStatus(m.status)) && unreachable.forall(m Gossip.convergenceSkipUnreachableWithMemberStatus(m.status)) &&
!members.exists(m Gossip.convergenceMemberStatus(m.status) && !seenByNode(m.uniqueAddress)) !members.exists(m Gossip.convergenceMemberStatus(m.status) && !seenByNode(m.uniqueAddress))
} }
lazy val reachabilityExcludingDownedObservers: Reachability = {
val downed = members.collect { case m if m.status == Down m }
overview.reachability.removeObservers(downed.map(_.uniqueAddress))
}
def isLeader(node: UniqueAddress): Boolean = leader == Some(node) def isLeader(node: UniqueAddress): Boolean = leader == Some(node)
def leader: Option[UniqueAddress] = leaderOf(members) def leader: Option[UniqueAddress] = leaderOf(members)

View file

@ -187,6 +187,18 @@ private[cluster] class Reachability private (
} }
} }
def removeObservers(nodes: Set[UniqueAddress]): Reachability =
if (nodes.isEmpty)
this
else {
val newRecords = records.filterNot(r nodes(r.observer))
if (newRecords.size == records.size) this
else {
val newVersions = versions -- nodes
Reachability(newRecords, newVersions)
}
}
def status(observer: UniqueAddress, subject: UniqueAddress): ReachabilityStatus = def status(observer: UniqueAddress, subject: UniqueAddress): ReachabilityStatus =
observerRows(observer) match { observerRows(observer) match {
case None Reachable case None Reachable

View file

@ -19,8 +19,15 @@ import akka.testkit.ImplicitSender
import akka.actor.ActorRef import akka.actor.ActorRef
import akka.testkit.TestProbe import akka.testkit.TestProbe
object ClusterDomainEventPublisherSpec {
val config = """
akka.actor.provider = "akka.cluster.ClusterActorRefProvider"
akka.remote.netty.tcp.port = 0
"""
}
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner]) @org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
class ClusterDomainEventPublisherSpec extends AkkaSpec class ClusterDomainEventPublisherSpec extends AkkaSpec(ClusterDomainEventPublisherSpec.config)
with BeforeAndAfterEach with ImplicitSender { with BeforeAndAfterEach with ImplicitSender {
var publisher: ActorRef = _ var publisher: ActorRef = _

View file

@ -34,6 +34,7 @@ class ClusterDomainEventSpec extends WordSpec with Matchers {
val eJoining = TestMember(Address("akka.tcp", "sys", "e", 2552), Joining, eRoles) val eJoining = TestMember(Address("akka.tcp", "sys", "e", 2552), Joining, eRoles)
val eUp = TestMember(Address("akka.tcp", "sys", "e", 2552), Up, eRoles) val eUp = TestMember(Address("akka.tcp", "sys", "e", 2552), Up, eRoles)
val eDown = TestMember(Address("akka.tcp", "sys", "e", 2552), Down, eRoles) val eDown = TestMember(Address("akka.tcp", "sys", "e", 2552), Down, eRoles)
val selfDummyAddress = UniqueAddress(Address("akka.tcp", "sys", "selfDummy", 2552), 17)
private[cluster] def converge(gossip: Gossip): (Gossip, Set[UniqueAddress]) = private[cluster] def converge(gossip: Gossip): (Gossip, Set[UniqueAddress]) =
((gossip, Set.empty[UniqueAddress]) /: gossip.members) { case ((gs, as), m) (gs.seen(m.uniqueAddress), as + m.uniqueAddress) } ((gossip, Set.empty[UniqueAddress]) /: gossip.members) { case ((gs, as), m) (gs.seen(m.uniqueAddress), as + m.uniqueAddress) }
@ -43,7 +44,7 @@ class ClusterDomainEventSpec extends WordSpec with Matchers {
"be empty for the same gossip" in { "be empty for the same gossip" in {
val g1 = Gossip(members = SortedSet(aUp)) val g1 = Gossip(members = SortedSet(aUp))
diffUnreachable(g1, g1) should be(Seq.empty) diffUnreachable(g1, g1, selfDummyAddress) should be(Seq.empty)
} }
"be produced for new members" in { "be produced for new members" in {
@ -51,8 +52,8 @@ class ClusterDomainEventSpec extends WordSpec with Matchers {
val (g2, s2) = converge(Gossip(members = SortedSet(aUp, bUp, eJoining))) val (g2, s2) = converge(Gossip(members = SortedSet(aUp, bUp, eJoining)))
diffMemberEvents(g1, g2) should be(Seq(MemberUp(bUp))) diffMemberEvents(g1, g2) should be(Seq(MemberUp(bUp)))
diffUnreachable(g1, g2) should be(Seq.empty) diffUnreachable(g1, g2, selfDummyAddress) should be(Seq.empty)
diffSeen(g1, g2) should be(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address)))) diffSeen(g1, g2, selfDummyAddress) should be(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address))))
} }
"be produced for changed status of members" in { "be produced for changed status of members" in {
@ -60,8 +61,8 @@ class ClusterDomainEventSpec extends WordSpec with Matchers {
val (g2, s2) = converge(Gossip(members = SortedSet(aUp, bUp, cLeaving, eJoining))) val (g2, s2) = converge(Gossip(members = SortedSet(aUp, bUp, cLeaving, eJoining)))
diffMemberEvents(g1, g2) should be(Seq(MemberUp(aUp))) diffMemberEvents(g1, g2) should be(Seq(MemberUp(aUp)))
diffUnreachable(g1, g2) should be(Seq.empty) diffUnreachable(g1, g2, selfDummyAddress) should be(Seq.empty)
diffSeen(g1, g2) should be(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address)))) diffSeen(g1, g2, selfDummyAddress) should be(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address))))
} }
"be produced for members in unreachable" in { "be produced for members in unreachable" in {
@ -73,8 +74,10 @@ class ClusterDomainEventSpec extends WordSpec with Matchers {
unreachable(aUp.uniqueAddress, bDown.uniqueAddress) unreachable(aUp.uniqueAddress, bDown.uniqueAddress)
val g2 = Gossip(members = SortedSet(aUp, cUp, bDown, eDown), overview = GossipOverview(reachability = reachability2)) val g2 = Gossip(members = SortedSet(aUp, cUp, bDown, eDown), overview = GossipOverview(reachability = reachability2))
diffUnreachable(g1, g2) should be(Seq(UnreachableMember(bDown))) diffUnreachable(g1, g2, selfDummyAddress) should be(Seq(UnreachableMember(bDown)))
diffSeen(g1, g2) should be(Seq.empty) // never include self member in unreachable
diffUnreachable(g1, g2, bDown.uniqueAddress) should be(Seq())
diffSeen(g1, g2, selfDummyAddress) should be(Seq.empty)
} }
"be produced for members becoming reachable after unreachable" in { "be produced for members becoming reachable after unreachable" in {
@ -88,8 +91,12 @@ class ClusterDomainEventSpec extends WordSpec with Matchers {
reachable(aUp.uniqueAddress, bUp.uniqueAddress) reachable(aUp.uniqueAddress, bUp.uniqueAddress)
val g2 = Gossip(members = SortedSet(aUp, cUp, bUp, eUp), overview = GossipOverview(reachability = reachability2)) val g2 = Gossip(members = SortedSet(aUp, cUp, bUp, eUp), overview = GossipOverview(reachability = reachability2))
diffUnreachable(g1, g2) should be(Seq(UnreachableMember(cUp))) diffUnreachable(g1, g2, selfDummyAddress) should be(Seq(UnreachableMember(cUp)))
diffReachable(g1, g2) should be(Seq(ReachableMember(bUp))) // never include self member in unreachable
diffUnreachable(g1, g2, cUp.uniqueAddress) should be(Seq())
diffReachable(g1, g2, selfDummyAddress) should be(Seq(ReachableMember(bUp)))
// never include self member in reachable
diffReachable(g1, g2, bUp.uniqueAddress) should be(Seq())
} }
"be produced for removed members" in { "be produced for removed members" in {
@ -97,8 +104,8 @@ class ClusterDomainEventSpec extends WordSpec with Matchers {
val (g2, s2) = converge(Gossip(members = SortedSet(aUp))) val (g2, s2) = converge(Gossip(members = SortedSet(aUp)))
diffMemberEvents(g1, g2) should be(Seq(MemberRemoved(dRemoved, Exiting))) diffMemberEvents(g1, g2) should be(Seq(MemberRemoved(dRemoved, Exiting)))
diffUnreachable(g1, g2) should be(Seq.empty) diffUnreachable(g1, g2, selfDummyAddress) should be(Seq.empty)
diffSeen(g1, g2) should be(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address)))) diffSeen(g1, g2, selfDummyAddress) should be(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address))))
} }
"be produced for convergence changes" in { "be produced for convergence changes" in {
@ -106,11 +113,11 @@ class ClusterDomainEventSpec extends WordSpec with Matchers {
val g2 = Gossip(members = SortedSet(aUp, bUp, eJoining)).seen(aUp.uniqueAddress).seen(bUp.uniqueAddress) val g2 = Gossip(members = SortedSet(aUp, bUp, eJoining)).seen(aUp.uniqueAddress).seen(bUp.uniqueAddress)
diffMemberEvents(g1, g2) should be(Seq.empty) diffMemberEvents(g1, g2) should be(Seq.empty)
diffUnreachable(g1, g2) should be(Seq.empty) diffUnreachable(g1, g2, selfDummyAddress) should be(Seq.empty)
diffSeen(g1, g2) should be(Seq(SeenChanged(convergence = true, seenBy = Set(aUp.address, bUp.address)))) diffSeen(g1, g2, selfDummyAddress) should be(Seq(SeenChanged(convergence = true, seenBy = Set(aUp.address, bUp.address))))
diffMemberEvents(g2, g1) should be(Seq.empty) diffMemberEvents(g2, g1) should be(Seq.empty)
diffUnreachable(g2, g1) should be(Seq.empty) diffUnreachable(g2, g1, selfDummyAddress) should be(Seq.empty)
diffSeen(g2, g1) should be(Seq(SeenChanged(convergence = true, seenBy = Set(aUp.address, bUp.address, eJoining.address)))) diffSeen(g2, g1, selfDummyAddress) should be(Seq(SeenChanged(convergence = true, seenBy = Set(aUp.address, bUp.address, eJoining.address))))
} }
"be produced for leader changes" in { "be produced for leader changes" in {
@ -118,8 +125,8 @@ class ClusterDomainEventSpec extends WordSpec with Matchers {
val (g2, s2) = converge(Gossip(members = SortedSet(bUp, eJoining))) val (g2, s2) = converge(Gossip(members = SortedSet(bUp, eJoining)))
diffMemberEvents(g1, g2) should be(Seq(MemberRemoved(aRemoved, Up))) diffMemberEvents(g1, g2) should be(Seq(MemberRemoved(aRemoved, Up)))
diffUnreachable(g1, g2) should be(Seq.empty) diffUnreachable(g1, g2, selfDummyAddress) should be(Seq.empty)
diffSeen(g1, g2) should be(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address)))) diffSeen(g1, g2, selfDummyAddress) should be(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address))))
diffLeader(g1, g2) should be(Seq(LeaderChanged(Some(bUp.address)))) diffLeader(g1, g2) should be(Seq(LeaderChanged(Some(bUp.address))))
} }

View file

@ -29,7 +29,51 @@ class GossipSpec extends WordSpec with Matchers {
"A Gossip" must { "A Gossip" must {
"reach convergence when it's empty" in { "reach convergence when it's empty" in {
Gossip.empty.convergence should be(true) Gossip.empty.convergence(a1.uniqueAddress) should be(true)
}
"reach convergence for one node" in {
val g1 = (Gossip(members = SortedSet(a1))).seen(a1.uniqueAddress)
g1.convergence(a1.uniqueAddress) should be(true)
}
"not reach convergence until all have seen version" in {
val g1 = (Gossip(members = SortedSet(a1, b1))).seen(a1.uniqueAddress)
g1.convergence(a1.uniqueAddress) should be(false)
}
"reach convergence for two nodes" in {
val g1 = (Gossip(members = SortedSet(a1, b1))).seen(a1.uniqueAddress).seen(b1.uniqueAddress)
g1.convergence(a1.uniqueAddress) should be(true)
}
"reach convergence, skipping joining" in {
// e1 is joining
val g1 = (Gossip(members = SortedSet(a1, b1, e1))).seen(a1.uniqueAddress).seen(b1.uniqueAddress)
g1.convergence(a1.uniqueAddress) should be(true)
}
"reach convergence, skipping down" in {
// e3 is down
val g1 = (Gossip(members = SortedSet(a1, b1, e3))).seen(a1.uniqueAddress).seen(b1.uniqueAddress)
g1.convergence(a1.uniqueAddress) should be(true)
}
"not reach convergence when unreachable" in {
val r1 = Reachability.empty.unreachable(b1.uniqueAddress, a1.uniqueAddress)
val g1 = (Gossip(members = SortedSet(a1, b1), overview = GossipOverview(reachability = r1)))
.seen(a1.uniqueAddress).seen(b1.uniqueAddress)
g1.convergence(b1.uniqueAddress) should be(false)
// but from a1's point of view (it knows that itself is not unreachable)
g1.convergence(a1.uniqueAddress) should be(true)
}
"reach convergence when downed node has observed unreachable" in {
// e3 is Down
val r1 = Reachability.empty.unreachable(e3.uniqueAddress, a1.uniqueAddress)
val g1 = (Gossip(members = SortedSet(a1, b1, e3), overview = GossipOverview(reachability = r1)))
.seen(a1.uniqueAddress).seen(b1.uniqueAddress).seen(e3.uniqueAddress)
g1.convergence(b1.uniqueAddress) should be(true)
} }
"merge members by status priority" in { "merge members by status priority" in {

View file

@ -64,6 +64,19 @@ class ReachabilitySpec extends WordSpec with Matchers {
r.isReachable(nodeA) should be(true) r.isReachable(nodeA) should be(true)
} }
"exclude observations from specific (downed) nodes" in {
val r = Reachability.empty.
unreachable(nodeC, nodeA).reachable(nodeC, nodeA).
unreachable(nodeC, nodeB).
unreachable(nodeB, nodeA).unreachable(nodeB, nodeC)
r.isReachable(nodeA) should be(false)
r.isReachable(nodeB) should be(false)
r.isReachable(nodeC) should be(false)
r.allUnreachableOrTerminated should be(Set(nodeA, nodeB, nodeC))
r.removeObservers(Set(nodeB)).allUnreachableOrTerminated should be(Set(nodeB))
}
"be pruned when all records of an observer are Reachable" in { "be pruned when all records of an observer are Reachable" in {
val r = Reachability.empty. val r = Reachability.empty.
unreachable(nodeB, nodeA).unreachable(nodeB, nodeC). unreachable(nodeB, nodeA).unreachable(nodeB, nodeC).