Refactoring of Gossip class, #23290
* move methods that depends on selfUniqueAddress and selfDc to a separate MembershipState class, which also holds the latest gossip * this removes the need to pass in the parameters from everywhere and makes it easier to cache some results * makes it clear that those parameters are always selfUniqueAddress and selfDc, instead of some arbitary node/dc
This commit is contained in:
parent
dee14c5b20
commit
867cc97bdd
10 changed files with 386 additions and 321 deletions
|
|
@ -101,7 +101,7 @@ private[cluster] abstract class AutoDownBase(autoDownUnreachableAfter: FiniteDur
|
||||||
|
|
||||||
import context.dispatcher
|
import context.dispatcher
|
||||||
|
|
||||||
val skipMemberStatus = Gossip.convergenceSkipUnreachableWithMemberStatus
|
val skipMemberStatus = MembershipState.convergenceSkipUnreachableWithMemberStatus
|
||||||
|
|
||||||
var scheduledUnreachable: Map[UniqueAddress, Cancellable] = Map.empty
|
var scheduledUnreachable: Map[UniqueAddress, Cancellable] = Map.empty
|
||||||
var pendingUnreachable: Set[UniqueAddress] = Set.empty
|
var pendingUnreachable: Set[UniqueAddress] = Set.empty
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@ import akka.actor._
|
||||||
import akka.actor.SupervisorStrategy.Stop
|
import akka.actor.SupervisorStrategy.Stop
|
||||||
import akka.cluster.MemberStatus._
|
import akka.cluster.MemberStatus._
|
||||||
import akka.cluster.ClusterEvent._
|
import akka.cluster.ClusterEvent._
|
||||||
|
import akka.cluster.ClusterSettings.DataCenter
|
||||||
import akka.dispatch.{ RequiresMessageQueue, UnboundedMessageQueueSemantics }
|
import akka.dispatch.{ RequiresMessageQueue, UnboundedMessageQueueSemantics }
|
||||||
|
|
||||||
import scala.collection.breakOut
|
import scala.collection.breakOut
|
||||||
|
|
@ -157,7 +158,7 @@ private[cluster] object InternalClusterAction {
|
||||||
final case class SendCurrentClusterState(receiver: ActorRef) extends SubscriptionMessage
|
final case class SendCurrentClusterState(receiver: ActorRef) extends SubscriptionMessage
|
||||||
|
|
||||||
sealed trait PublishMessage
|
sealed trait PublishMessage
|
||||||
final case class PublishChanges(newGossip: Gossip) extends PublishMessage
|
final case class PublishChanges(state: MembershipState) extends PublishMessage
|
||||||
final case class PublishEvent(event: ClusterDomainEvent) extends PublishMessage
|
final case class PublishEvent(event: ClusterDomainEvent) extends PublishMessage
|
||||||
|
|
||||||
final case object ExitingCompleted
|
final case object ExitingCompleted
|
||||||
|
|
@ -277,6 +278,7 @@ private[cluster] object ClusterCoreDaemon {
|
||||||
|
|
||||||
val NumberOfGossipsBeforeShutdownWhenLeaderExits = 5
|
val NumberOfGossipsBeforeShutdownWhenLeaderExits = 5
|
||||||
val MaxGossipsBeforeShuttingDownMyself = 5
|
val MaxGossipsBeforeShuttingDownMyself = 5
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -287,6 +289,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
with RequiresMessageQueue[UnboundedMessageQueueSemantics] {
|
with RequiresMessageQueue[UnboundedMessageQueueSemantics] {
|
||||||
import InternalClusterAction._
|
import InternalClusterAction._
|
||||||
import ClusterCoreDaemon._
|
import ClusterCoreDaemon._
|
||||||
|
import MembershipState._
|
||||||
|
|
||||||
val cluster = Cluster(context.system)
|
val cluster = Cluster(context.system)
|
||||||
import cluster.{ selfAddress, selfRoles, scheduler, failureDetector }
|
import cluster.{ selfAddress, selfRoles, scheduler, failureDetector }
|
||||||
|
|
@ -299,7 +302,8 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
|
|
||||||
// note that self is not initially member,
|
// note that self is not initially member,
|
||||||
// and the Gossip is not versioned for this 'Node' yet
|
// and the Gossip is not versioned for this 'Node' yet
|
||||||
var latestGossip: Gossip = Gossip.empty
|
var membershipState = MembershipState(Gossip.empty, cluster.selfUniqueAddress, cluster.settings.DataCenter)
|
||||||
|
def latestGossip: Gossip = membershipState.latestGossip
|
||||||
|
|
||||||
val statsEnabled = PublishStatsInterval.isFinite
|
val statsEnabled = PublishStatsInterval.isFinite
|
||||||
var gossipStats = GossipStats()
|
var gossipStats = GossipStats()
|
||||||
|
|
@ -478,7 +482,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
|
|
||||||
def initJoin(): Unit = {
|
def initJoin(): Unit = {
|
||||||
val selfStatus = latestGossip.member(selfUniqueAddress).status
|
val selfStatus = latestGossip.member(selfUniqueAddress).status
|
||||||
if (Gossip.removeUnreachableWithMemberStatus.contains(selfStatus)) {
|
if (removeUnreachableWithMemberStatus.contains(selfStatus)) {
|
||||||
// prevents a Down and Exiting node from being used for joining
|
// prevents a Down and Exiting node from being used for joining
|
||||||
logInfo("Sending InitJoinNack message from node [{}] to [{}]", selfAddress, sender())
|
logInfo("Sending InitJoinNack message from node [{}] to [{}]", selfAddress, sender())
|
||||||
sender() ! InitJoinNack(selfAddress)
|
sender() ! InitJoinNack(selfAddress)
|
||||||
|
|
@ -570,7 +574,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
log.warning(
|
log.warning(
|
||||||
"Member with wrong ActorSystem name tried to join, but was ignored, expected [{}] but was [{}]",
|
"Member with wrong ActorSystem name tried to join, but was ignored, expected [{}] but was [{}]",
|
||||||
selfAddress.system, joiningNode.address.system)
|
selfAddress.system, joiningNode.address.system)
|
||||||
else if (Gossip.removeUnreachableWithMemberStatus.contains(selfStatus))
|
else if (removeUnreachableWithMemberStatus.contains(selfStatus))
|
||||||
logInfo("Trying to join [{}] to [{}] member, ignoring. Use a member that is Up instead.", joiningNode, selfStatus)
|
logInfo("Trying to join [{}] to [{}] member, ignoring. Use a member that is Up instead.", joiningNode, selfStatus)
|
||||||
else {
|
else {
|
||||||
val localMembers = latestGossip.members
|
val localMembers = latestGossip.members
|
||||||
|
|
@ -616,7 +620,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
} else
|
} else
|
||||||
sender() ! Welcome(selfUniqueAddress, latestGossip)
|
sender() ! Welcome(selfUniqueAddress, latestGossip)
|
||||||
|
|
||||||
publish(latestGossip)
|
publishMembershipState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -629,10 +633,10 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
if (joinWith != from.address)
|
if (joinWith != from.address)
|
||||||
logInfo("Ignoring welcome from [{}] when trying to join with [{}]", from.address, joinWith)
|
logInfo("Ignoring welcome from [{}] when trying to join with [{}]", from.address, joinWith)
|
||||||
else {
|
else {
|
||||||
latestGossip = gossip seen selfUniqueAddress
|
membershipState = membershipState.copy(latestGossip = gossip).seen()
|
||||||
logInfo("Welcome from [{}]", from.address)
|
logInfo("Welcome from [{}]", from.address)
|
||||||
assertLatestGossip()
|
assertLatestGossip()
|
||||||
publish(latestGossip)
|
publishMembershipState()
|
||||||
if (from != selfUniqueAddress)
|
if (from != selfUniqueAddress)
|
||||||
gossipTo(from, sender())
|
gossipTo(from, sender())
|
||||||
becomeInitialized()
|
becomeInitialized()
|
||||||
|
|
@ -653,7 +657,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
updateLatestGossip(newGossip)
|
updateLatestGossip(newGossip)
|
||||||
|
|
||||||
logInfo("Marked address [{}] as [{}]", address, Leaving)
|
logInfo("Marked address [{}] as [{}]", address, Leaving)
|
||||||
publish(latestGossip)
|
publishMembershipState()
|
||||||
// immediate gossip to speed up the leaving process
|
// immediate gossip to speed up the leaving process
|
||||||
gossip()
|
gossip()
|
||||||
}
|
}
|
||||||
|
|
@ -664,9 +668,9 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
// ExitingCompleted sent via CoordinatedShutdown to continue the leaving process.
|
// ExitingCompleted sent via CoordinatedShutdown to continue the leaving process.
|
||||||
exitingTasksInProgress = false
|
exitingTasksInProgress = false
|
||||||
// mark as seen
|
// mark as seen
|
||||||
latestGossip = latestGossip seen selfUniqueAddress
|
membershipState = membershipState.seen()
|
||||||
assertLatestGossip()
|
assertLatestGossip()
|
||||||
publish(latestGossip)
|
publishMembershipState()
|
||||||
|
|
||||||
// Let others know (best effort) before shutdown. Otherwise they will not see
|
// Let others know (best effort) before shutdown. Otherwise they will not see
|
||||||
// convergence of the Exiting state until they have detected this node as
|
// convergence of the Exiting state until they have detected this node as
|
||||||
|
|
@ -681,10 +685,10 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
// send ExitingConfirmed to two potential leaders
|
// send ExitingConfirmed to two potential leaders
|
||||||
val membersExceptSelf = latestGossip.members.filter(_.uniqueAddress != selfUniqueAddress)
|
val membersExceptSelf = latestGossip.members.filter(_.uniqueAddress != selfUniqueAddress)
|
||||||
|
|
||||||
latestGossip.leaderOf(selfDc, membersExceptSelf, selfUniqueAddress) match {
|
membershipState.leaderOf(membersExceptSelf) match {
|
||||||
case Some(node1) ⇒
|
case Some(node1) ⇒
|
||||||
clusterCore(node1.address) ! ExitingConfirmed(selfUniqueAddress)
|
clusterCore(node1.address) ! ExitingConfirmed(selfUniqueAddress)
|
||||||
latestGossip.leaderOf(selfDc, membersExceptSelf.filterNot(_.uniqueAddress == node1), selfUniqueAddress) match {
|
membershipState.leaderOf(membersExceptSelf.filterNot(_.uniqueAddress == node1)) match {
|
||||||
case Some(node2) ⇒
|
case Some(node2) ⇒
|
||||||
clusterCore(node2.address) ! ExitingConfirmed(selfUniqueAddress)
|
clusterCore(node2.address) ! ExitingConfirmed(selfUniqueAddress)
|
||||||
case None ⇒ // no more potential leader
|
case None ⇒ // no more potential leader
|
||||||
|
|
@ -723,7 +727,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
val localMembers = localGossip.members
|
val localMembers = localGossip.members
|
||||||
val localOverview = localGossip.overview
|
val localOverview = localGossip.overview
|
||||||
val localSeen = localOverview.seen
|
val localSeen = localOverview.seen
|
||||||
val localReachability = localGossip.dcReachability(selfDc)
|
val localReachability = membershipState.dcReachability
|
||||||
|
|
||||||
// check if the node to DOWN is in the `members` set
|
// check if the node to DOWN is in the `members` set
|
||||||
localMembers.find(_.address == address) match {
|
localMembers.find(_.address == address) match {
|
||||||
|
|
@ -735,7 +739,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
|
|
||||||
val newGossip = localGossip.markAsDown(m)
|
val newGossip = localGossip.markAsDown(m)
|
||||||
updateLatestGossip(newGossip)
|
updateLatestGossip(newGossip)
|
||||||
publish(latestGossip)
|
publishMembershipState()
|
||||||
case Some(_) ⇒ // already down
|
case Some(_) ⇒ // already down
|
||||||
case None ⇒
|
case None ⇒
|
||||||
logInfo("Ignoring down of unknown node [{}]", address)
|
logInfo("Ignoring down of unknown node [{}]", address)
|
||||||
|
|
@ -753,7 +757,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
log.warning(
|
log.warning(
|
||||||
"Cluster Node [{}] - Marking node as TERMINATED [{}], due to quarantine. Node roles [{}]",
|
"Cluster Node [{}] - Marking node as TERMINATED [{}], due to quarantine. Node roles [{}]",
|
||||||
selfAddress, node.address, selfRoles.mkString(","))
|
selfAddress, node.address, selfRoles.mkString(","))
|
||||||
publish(latestGossip)
|
publishMembershipState()
|
||||||
downing(node.address)
|
downing(node.address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -829,14 +833,14 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
// Perform the same pruning (clear of VectorClock) as the leader did when removing a member.
|
// Perform the same pruning (clear of VectorClock) as the leader did when removing a member.
|
||||||
// Removal of member itself is handled in merge (pickHighestPriority)
|
// Removal of member itself is handled in merge (pickHighestPriority)
|
||||||
val prunedLocalGossip = localGossip.members.foldLeft(localGossip) { (g, m) ⇒
|
val prunedLocalGossip = localGossip.members.foldLeft(localGossip) { (g, m) ⇒
|
||||||
if (Gossip.removeUnreachableWithMemberStatus(m.status) && !remoteGossip.members.contains(m)) {
|
if (removeUnreachableWithMemberStatus(m.status) && !remoteGossip.members.contains(m)) {
|
||||||
log.debug("Cluster Node [{}] - Pruned conflicting local gossip: {}", selfAddress, m)
|
log.debug("Cluster Node [{}] - Pruned conflicting local gossip: {}", selfAddress, m)
|
||||||
g.prune(VectorClock.Node(vclockName(m.uniqueAddress)))
|
g.prune(VectorClock.Node(vclockName(m.uniqueAddress)))
|
||||||
} else
|
} else
|
||||||
g
|
g
|
||||||
}
|
}
|
||||||
val prunedRemoteGossip = remoteGossip.members.foldLeft(remoteGossip) { (g, m) ⇒
|
val prunedRemoteGossip = remoteGossip.members.foldLeft(remoteGossip) { (g, m) ⇒
|
||||||
if (Gossip.removeUnreachableWithMemberStatus(m.status) && !localGossip.members.contains(m)) {
|
if (removeUnreachableWithMemberStatus(m.status) && !localGossip.members.contains(m)) {
|
||||||
log.debug("Cluster Node [{}] - Pruned conflicting remote gossip: {}", selfAddress, m)
|
log.debug("Cluster Node [{}] - Pruned conflicting remote gossip: {}", selfAddress, m)
|
||||||
g.prune(VectorClock.Node(vclockName(m.uniqueAddress)))
|
g.prune(VectorClock.Node(vclockName(m.uniqueAddress)))
|
||||||
} else
|
} else
|
||||||
|
|
@ -849,9 +853,9 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
// Don't mark gossip state as seen while exiting is in progress, e.g.
|
// Don't mark gossip state as seen while exiting is in progress, e.g.
|
||||||
// shutting down singleton actors. This delays removal of the member until
|
// shutting down singleton actors. This delays removal of the member until
|
||||||
// the exiting tasks have been completed.
|
// the exiting tasks have been completed.
|
||||||
latestGossip =
|
membershipState = membershipState.copy(latestGossip =
|
||||||
if (exitingTasksInProgress) winningGossip
|
if (exitingTasksInProgress) winningGossip
|
||||||
else winningGossip seen selfUniqueAddress
|
else winningGossip seen selfUniqueAddress)
|
||||||
assertLatestGossip()
|
assertLatestGossip()
|
||||||
|
|
||||||
// for all new joining nodes we remove them from the failure detector
|
// for all new joining nodes we remove them from the failure detector
|
||||||
|
|
@ -877,7 +881,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
publish(latestGossip)
|
publishMembershipState()
|
||||||
|
|
||||||
val selfStatus = latestGossip.member(selfUniqueAddress).status
|
val selfStatus = latestGossip.member(selfUniqueAddress).status
|
||||||
if (selfStatus == Exiting && !exitingTasksInProgress) {
|
if (selfStatus == Exiting && !exitingTasksInProgress) {
|
||||||
|
|
@ -1004,11 +1008,11 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
* Runs periodic leader actions, such as member status transitions, assigning partitions etc.
|
* Runs periodic leader actions, such as member status transitions, assigning partitions etc.
|
||||||
*/
|
*/
|
||||||
def leaderActions(): Unit = {
|
def leaderActions(): Unit = {
|
||||||
if (latestGossip.isDcLeader(selfDc, selfUniqueAddress, selfUniqueAddress)) {
|
if (membershipState.isLeader(selfUniqueAddress)) {
|
||||||
// only run the leader actions if we are the LEADER of the data center
|
// only run the leader actions if we are the LEADER of the data center
|
||||||
val firstNotice = 20
|
val firstNotice = 20
|
||||||
val periodicNotice = 60
|
val periodicNotice = 60
|
||||||
if (latestGossip.convergence(selfDc, selfUniqueAddress, exitingConfirmed)) {
|
if (membershipState.convergence(exitingConfirmed)) {
|
||||||
if (leaderActionCounter >= firstNotice)
|
if (leaderActionCounter >= firstNotice)
|
||||||
logInfo("Leader can perform its duties again")
|
logInfo("Leader can perform its duties again")
|
||||||
leaderActionCounter = 0
|
leaderActionCounter = 0
|
||||||
|
|
@ -1021,7 +1025,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
if (leaderActionCounter == firstNotice || leaderActionCounter % periodicNotice == 0)
|
if (leaderActionCounter == firstNotice || leaderActionCounter % periodicNotice == 0)
|
||||||
logInfo(
|
logInfo(
|
||||||
"Leader can currently not perform its duties, reachability status: [{}], member status: [{}]",
|
"Leader can currently not perform its duties, reachability status: [{}], member status: [{}]",
|
||||||
latestGossip.dcReachabilityExcludingDownedObservers(selfDc),
|
membershipState.dcReachabilityExcludingDownedObservers,
|
||||||
latestGossip.members.collect {
|
latestGossip.members.collect {
|
||||||
case m if m.dataCenter == selfDc ⇒
|
case m if m.dataCenter == selfDc ⇒
|
||||||
s"${m.address} ${m.status} seen=${latestGossip.seenByNode(m.uniqueAddress)}"
|
s"${m.address} ${m.status} seen=${latestGossip.seenByNode(m.uniqueAddress)}"
|
||||||
|
|
@ -1036,8 +1040,8 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
if (latestGossip.member(selfUniqueAddress).status == Down) {
|
if (latestGossip.member(selfUniqueAddress).status == Down) {
|
||||||
// When all reachable have seen the state this member will shutdown itself when it has
|
// When all reachable have seen the state this member will shutdown itself when it has
|
||||||
// status Down. The down commands should spread before we shutdown.
|
// status Down. The down commands should spread before we shutdown.
|
||||||
val unreachable = latestGossip.dcReachability(selfDc).allUnreachableOrTerminated
|
val unreachable = membershipState.dcReachability.allUnreachableOrTerminated
|
||||||
val downed = latestGossip.dcMembers(selfDc).collect { case m if m.status == Down ⇒ m.uniqueAddress }
|
val downed = membershipState.dcMembers.collect { case m if m.status == Down ⇒ m.uniqueAddress }
|
||||||
if (downed.forall(node ⇒ unreachable(node) || latestGossip.seenByNode(node))) {
|
if (downed.forall(node ⇒ unreachable(node) || latestGossip.seenByNode(node))) {
|
||||||
// the reason for not shutting down immediately is to give the gossip a chance to spread
|
// the reason for not shutting down immediately is to give the gossip a chance to spread
|
||||||
// the downing information to other downed nodes, so that they can shutdown themselves
|
// the downing information to other downed nodes, so that they can shutdown themselves
|
||||||
|
|
@ -1072,9 +1076,9 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
def leaderActionsOnConvergence(): Unit = {
|
def leaderActionsOnConvergence(): Unit = {
|
||||||
|
|
||||||
val removedUnreachable = for {
|
val removedUnreachable = for {
|
||||||
node ← latestGossip.dcReachability(selfDc).allUnreachableOrTerminated
|
node ← membershipState.dcReachability.allUnreachableOrTerminated
|
||||||
m = latestGossip.member(node)
|
m = latestGossip.member(node)
|
||||||
if m.dataCenter == selfDc && Gossip.removeUnreachableWithMemberStatus(m.status)
|
if m.dataCenter == selfDc && removeUnreachableWithMemberStatus(m.status)
|
||||||
} yield m
|
} yield m
|
||||||
|
|
||||||
val removedExitingConfirmed = exitingConfirmed.filter { n ⇒
|
val removedExitingConfirmed = exitingConfirmed.filter { n ⇒
|
||||||
|
|
@ -1148,7 +1152,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
val pruned = updatedGossip.pruneTombstones(System.currentTimeMillis() - PruneGossipTombstonesAfter.toMillis)
|
val pruned = updatedGossip.pruneTombstones(System.currentTimeMillis() - PruneGossipTombstonesAfter.toMillis)
|
||||||
if (pruned ne latestGossip) {
|
if (pruned ne latestGossip) {
|
||||||
updateLatestGossip(pruned)
|
updateLatestGossip(pruned)
|
||||||
publish(pruned)
|
publishMembershipState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1161,7 +1165,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
m.dataCenter == selfDc &&
|
m.dataCenter == selfDc &&
|
||||||
m.status == Joining &&
|
m.status == Joining &&
|
||||||
enoughMembers &&
|
enoughMembers &&
|
||||||
latestGossip.dcReachabilityExcludingDownedObservers(selfDc).isReachable(m.uniqueAddress)
|
membershipState.dcReachabilityExcludingDownedObservers.isReachable(m.uniqueAddress)
|
||||||
val changedMembers = localMembers.collect {
|
val changedMembers = localMembers.collect {
|
||||||
case m if isJoiningToWeaklyUp(m) ⇒ m.copy(status = WeaklyUp)
|
case m if isJoiningToWeaklyUp(m) ⇒ m.copy(status = WeaklyUp)
|
||||||
}
|
}
|
||||||
|
|
@ -1177,7 +1181,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
logInfo("Leader is moving node [{}] to [{}]", m.address, m.status)
|
logInfo("Leader is moving node [{}] to [{}]", m.address, m.status)
|
||||||
}
|
}
|
||||||
|
|
||||||
publish(latestGossip)
|
publishMembershipState()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1230,7 +1234,7 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
if (newlyDetectedReachableMembers.nonEmpty)
|
if (newlyDetectedReachableMembers.nonEmpty)
|
||||||
logInfo("Marking node(s) as REACHABLE [{}]. Node roles [{}]", newlyDetectedReachableMembers.mkString(", "), selfRoles.mkString(","))
|
logInfo("Marking node(s) as REACHABLE [{}]. Node roles [{}]", newlyDetectedReachableMembers.mkString(", "), selfRoles.mkString(","))
|
||||||
|
|
||||||
publish(latestGossip)
|
publishMembershipState()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1269,23 +1273,25 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
clusterCore(node.address) ! GossipStatus(selfUniqueAddress, latestGossip.version)
|
clusterCore(node.address) ! GossipStatus(selfUniqueAddress, latestGossip.version)
|
||||||
|
|
||||||
def validNodeForGossip(node: UniqueAddress): Boolean =
|
def validNodeForGossip(node: UniqueAddress): Boolean =
|
||||||
node != selfUniqueAddress && latestGossip.isReachableExcludingDownedObservers(selfDc, node)
|
node != selfUniqueAddress && membershipState.isReachableExcludingDownedObservers(node)
|
||||||
|
|
||||||
def updateLatestGossip(newGossip: Gossip): Unit = {
|
def updateLatestGossip(gossip: Gossip): Unit = {
|
||||||
// Updating the vclock version for the changes
|
// Updating the vclock version for the changes
|
||||||
val versionedGossip = newGossip :+ vclockNode
|
val versionedGossip = gossip :+ vclockNode
|
||||||
|
|
||||||
// Don't mark gossip state as seen while exiting is in progress, e.g.
|
// Don't mark gossip state as seen while exiting is in progress, e.g.
|
||||||
// shutting down singleton actors. This delays removal of the member until
|
// shutting down singleton actors. This delays removal of the member until
|
||||||
// the exiting tasks have been completed.
|
// the exiting tasks have been completed.
|
||||||
if (exitingTasksInProgress)
|
val newGossip =
|
||||||
latestGossip = versionedGossip.clearSeen()
|
if (exitingTasksInProgress)
|
||||||
else {
|
versionedGossip.clearSeen()
|
||||||
// Nobody else has seen this gossip but us
|
else {
|
||||||
val seenVersionedGossip = versionedGossip onlySeen (selfUniqueAddress)
|
// Nobody else has seen this gossip but us
|
||||||
// Update the state with the new gossip
|
val seenVersionedGossip = versionedGossip onlySeen (selfUniqueAddress)
|
||||||
latestGossip = seenVersionedGossip
|
// Update the state with the new gossip
|
||||||
}
|
seenVersionedGossip
|
||||||
|
}
|
||||||
|
membershipState = membershipState.copy(newGossip)
|
||||||
assertLatestGossip()
|
assertLatestGossip()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1293,11 +1299,11 @@ private[cluster] class ClusterCoreDaemon(publisher: ActorRef) extends Actor with
|
||||||
if (Cluster.isAssertInvariantsEnabled && latestGossip.version.versions.size > latestGossip.members.size)
|
if (Cluster.isAssertInvariantsEnabled && latestGossip.version.versions.size > latestGossip.members.size)
|
||||||
throw new IllegalStateException(s"Too many vector clock entries in gossip state ${latestGossip}")
|
throw new IllegalStateException(s"Too many vector clock entries in gossip state ${latestGossip}")
|
||||||
|
|
||||||
def publish(newGossip: Gossip): Unit = {
|
def publishMembershipState(): Unit = {
|
||||||
if (cluster.settings.Debug.VerboseGossipLogging)
|
if (cluster.settings.Debug.VerboseGossipLogging)
|
||||||
log.debug("Cluster Node [{}] dc [{}] - New gossip published [{}]", selfAddress, cluster.settings.DataCenter, newGossip)
|
log.debug("Cluster Node [{}] dc [{}] - New gossip published [{}]", selfAddress, cluster.settings.DataCenter, membershipState.latestGossip)
|
||||||
|
|
||||||
publisher ! PublishChanges(newGossip)
|
publisher ! PublishChanges(membershipState)
|
||||||
if (PublishStatsInterval == Duration.Zero) publishInternalStats()
|
if (PublishStatsInterval == Duration.Zero) publishInternalStats()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -265,12 +265,14 @@ object ClusterEvent {
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
private[cluster] def diffUnreachable(oldGossip: Gossip, newGossip: Gossip, selfUniqueAddress: UniqueAddress): immutable.Seq[UnreachableMember] =
|
private[cluster] def diffUnreachable(oldState: MembershipState, newState: MembershipState): immutable.Seq[UnreachableMember] =
|
||||||
if (newGossip eq oldGossip) Nil
|
if (newState eq oldState) Nil
|
||||||
else {
|
else {
|
||||||
|
val oldGossip = oldState.latestGossip
|
||||||
|
val newGossip = newState.latestGossip
|
||||||
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) && node != selfUniqueAddress ⇒
|
case node if !oldUnreachableNodes.contains(node) && node != newState.selfUniqueAddress ⇒
|
||||||
UnreachableMember(newGossip.member(node))
|
UnreachableMember(newGossip.member(node))
|
||||||
})(collection.breakOut)
|
})(collection.breakOut)
|
||||||
}
|
}
|
||||||
|
|
@ -278,11 +280,13 @@ object ClusterEvent {
|
||||||
/**
|
/**
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
private[cluster] def diffReachable(oldGossip: Gossip, newGossip: Gossip, selfUniqueAddress: UniqueAddress): immutable.Seq[ReachableMember] =
|
private[cluster] def diffReachable(oldState: MembershipState, newState: MembershipState): immutable.Seq[ReachableMember] =
|
||||||
if (newGossip eq oldGossip) Nil
|
if (newState eq oldState) Nil
|
||||||
else {
|
else {
|
||||||
(oldGossip.overview.reachability.allUnreachable.collect {
|
val oldGossip = oldState.latestGossip
|
||||||
case node if newGossip.hasMember(node) && newGossip.overview.reachability.isReachable(node) && node != selfUniqueAddress ⇒
|
val newGossip = newState.latestGossip
|
||||||
|
(oldState.overview.reachability.allUnreachable.collect {
|
||||||
|
case node if newGossip.hasMember(node) && newGossip.overview.reachability.isReachable(node) && node != newState.selfUniqueAddress ⇒
|
||||||
ReachableMember(newGossip.member(node))
|
ReachableMember(newGossip.member(node))
|
||||||
})(collection.breakOut)
|
})(collection.breakOut)
|
||||||
|
|
||||||
|
|
@ -291,9 +295,11 @@ object ClusterEvent {
|
||||||
/**
|
/**
|
||||||
* INTERNAL API.
|
* INTERNAL API.
|
||||||
*/
|
*/
|
||||||
private[cluster] def diffMemberEvents(oldGossip: Gossip, newGossip: Gossip): immutable.Seq[MemberEvent] =
|
private[cluster] def diffMemberEvents(oldState: MembershipState, newState: MembershipState): immutable.Seq[MemberEvent] =
|
||||||
if (newGossip eq oldGossip) Nil
|
if (newState eq oldState) Nil
|
||||||
else {
|
else {
|
||||||
|
val oldGossip = oldState.latestGossip
|
||||||
|
val newGossip = newState.latestGossip
|
||||||
val newMembers = newGossip.members diff oldGossip.members
|
val newMembers = newGossip.members diff oldGossip.members
|
||||||
val membersGroupedByAddress = List(newGossip.members, oldGossip.members).flatten.groupBy(_.uniqueAddress)
|
val membersGroupedByAddress = List(newGossip.members, oldGossip.members).flatten.groupBy(_.uniqueAddress)
|
||||||
val changedMembers = membersGroupedByAddress collect {
|
val changedMembers = membersGroupedByAddress collect {
|
||||||
|
|
@ -319,9 +325,9 @@ object ClusterEvent {
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
@InternalApi
|
@InternalApi
|
||||||
private[cluster] def diffLeader(dc: DataCenter, oldGossip: Gossip, newGossip: Gossip, selfUniqueAddress: UniqueAddress): immutable.Seq[LeaderChanged] = {
|
private[cluster] def diffLeader(oldState: MembershipState, newState: MembershipState): immutable.Seq[LeaderChanged] = {
|
||||||
val newLeader = newGossip.dcLeader(dc, selfUniqueAddress)
|
val newLeader = newState.leader
|
||||||
if (newLeader != oldGossip.dcLeader(dc, selfUniqueAddress)) List(LeaderChanged(newLeader.map(_.address)))
|
if (newLeader != oldState.leader) List(LeaderChanged(newLeader.map(_.address)))
|
||||||
else Nil
|
else Nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -329,11 +335,11 @@ object ClusterEvent {
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
@InternalApi
|
@InternalApi
|
||||||
private[cluster] def diffRolesLeader(dc: DataCenter, oldGossip: Gossip, newGossip: Gossip, selfUniqueAddress: UniqueAddress): Set[RoleLeaderChanged] = {
|
private[cluster] def diffRolesLeader(oldState: MembershipState, newState: MembershipState): Set[RoleLeaderChanged] = {
|
||||||
for {
|
for {
|
||||||
role ← oldGossip.allRoles union newGossip.allRoles
|
role ← oldState.latestGossip.allRoles union newState.latestGossip.allRoles
|
||||||
newLeader = newGossip.roleLeader(dc, role, selfUniqueAddress)
|
newLeader = newState.roleLeader(role)
|
||||||
if newLeader != oldGossip.roleLeader(dc, role, selfUniqueAddress)
|
if newLeader != oldState.roleLeader(role)
|
||||||
} yield RoleLeaderChanged(role, newLeader.map(_.address))
|
} yield RoleLeaderChanged(role, newLeader.map(_.address))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -341,12 +347,12 @@ object ClusterEvent {
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
@InternalApi
|
@InternalApi
|
||||||
private[cluster] def diffSeen(dc: DataCenter, oldGossip: Gossip, newGossip: Gossip, selfUniqueAddress: UniqueAddress): immutable.Seq[SeenChanged] =
|
private[cluster] def diffSeen(oldState: MembershipState, newState: MembershipState): immutable.Seq[SeenChanged] =
|
||||||
if (newGossip eq oldGossip) Nil
|
if (oldState eq newState) Nil
|
||||||
else {
|
else {
|
||||||
val newConvergence = newGossip.convergence(dc, selfUniqueAddress, Set.empty)
|
val newConvergence = newState.convergence(Set.empty)
|
||||||
val newSeenBy = newGossip.seenBy
|
val newSeenBy = newState.latestGossip.seenBy
|
||||||
if (newConvergence != oldGossip.convergence(dc, selfUniqueAddress, Set.empty) || newSeenBy != oldGossip.seenBy)
|
if (newConvergence != oldState.convergence(Set.empty) || newSeenBy != oldState.latestGossip.seenBy)
|
||||||
List(SeenChanged(newConvergence, newSeenBy.map(_.address)))
|
List(SeenChanged(newConvergence, newSeenBy.map(_.address)))
|
||||||
else Nil
|
else Nil
|
||||||
}
|
}
|
||||||
|
|
@ -355,9 +361,9 @@ object ClusterEvent {
|
||||||
* INTERNAL API
|
* INTERNAL API
|
||||||
*/
|
*/
|
||||||
@InternalApi
|
@InternalApi
|
||||||
private[cluster] def diffReachability(oldGossip: Gossip, newGossip: Gossip): immutable.Seq[ReachabilityChanged] =
|
private[cluster] def diffReachability(oldState: MembershipState, newState: MembershipState): immutable.Seq[ReachabilityChanged] =
|
||||||
if (newGossip.overview.reachability eq oldGossip.overview.reachability) Nil
|
if (newState.overview.reachability eq oldState.overview.reachability) Nil
|
||||||
else List(ReachabilityChanged(newGossip.overview.reachability))
|
else List(ReachabilityChanged(newState.overview.reachability))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -372,7 +378,8 @@ private[cluster] final class ClusterDomainEventPublisher extends Actor with Acto
|
||||||
|
|
||||||
val cluster = Cluster(context.system)
|
val cluster = Cluster(context.system)
|
||||||
val selfUniqueAddress = cluster.selfUniqueAddress
|
val selfUniqueAddress = cluster.selfUniqueAddress
|
||||||
var latestGossip: Gossip = Gossip.empty
|
val emptyMembershipState = MembershipState(Gossip.empty, cluster.selfUniqueAddress, cluster.settings.DataCenter)
|
||||||
|
var membershipState: MembershipState = emptyMembershipState
|
||||||
def selfDc = cluster.settings.DataCenter
|
def selfDc = cluster.settings.DataCenter
|
||||||
|
|
||||||
override def preRestart(reason: Throwable, message: Option[Any]) {
|
override def preRestart(reason: Throwable, message: Option[Any]) {
|
||||||
|
|
@ -382,11 +389,11 @@ private[cluster] final class ClusterDomainEventPublisher extends Actor with Acto
|
||||||
override def postStop(): Unit = {
|
override def postStop(): Unit = {
|
||||||
// publish the final removed state before shutting down
|
// publish the final removed state before shutting down
|
||||||
publish(ClusterShuttingDown)
|
publish(ClusterShuttingDown)
|
||||||
publishChanges(Gossip.empty)
|
publishChanges(emptyMembershipState)
|
||||||
}
|
}
|
||||||
|
|
||||||
def receive = {
|
def receive = {
|
||||||
case PublishChanges(newGossip) ⇒ publishChanges(newGossip)
|
case PublishChanges(newState) ⇒ publishChanges(newState)
|
||||||
case currentStats: CurrentInternalStats ⇒ publishInternalStats(currentStats)
|
case currentStats: CurrentInternalStats ⇒ publishInternalStats(currentStats)
|
||||||
case SendCurrentClusterState(receiver) ⇒ sendCurrentClusterState(receiver)
|
case SendCurrentClusterState(receiver) ⇒ sendCurrentClusterState(receiver)
|
||||||
case Subscribe(subscriber, initMode, to) ⇒ subscribe(subscriber, initMode, to)
|
case Subscribe(subscriber, initMode, to) ⇒ subscribe(subscriber, initMode, to)
|
||||||
|
|
@ -401,16 +408,17 @@ 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 {
|
val unreachable: Set[Member] =
|
||||||
case node if node != selfUniqueAddress ⇒ latestGossip.member(node)
|
membershipState.latestGossip.overview.reachability.allUnreachableOrTerminated.collect {
|
||||||
}
|
case node if node != selfUniqueAddress ⇒ membershipState.latestGossip.member(node)
|
||||||
|
}
|
||||||
val state = CurrentClusterState(
|
val state = CurrentClusterState(
|
||||||
members = latestGossip.members,
|
members = membershipState.latestGossip.members,
|
||||||
unreachable = unreachable,
|
unreachable = unreachable,
|
||||||
seenBy = latestGossip.seenBy.map(_.address),
|
seenBy = membershipState.latestGossip.seenBy.map(_.address),
|
||||||
leader = latestGossip.dcLeader(selfDc, selfUniqueAddress).map(_.address),
|
leader = membershipState.leader.map(_.address),
|
||||||
roleLeaderMap = latestGossip.allRoles.map(r ⇒
|
roleLeaderMap = membershipState.latestGossip.allRoles.map(r ⇒
|
||||||
r → latestGossip.roleLeader(selfDc, r, selfUniqueAddress).map(_.address))(collection.breakOut))
|
r → membershipState.roleLeader(r).map(_.address))(collection.breakOut))
|
||||||
receiver ! state
|
receiver ! state
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -421,7 +429,7 @@ private[cluster] final class ClusterDomainEventPublisher extends Actor with Acto
|
||||||
if (to.exists(_.isAssignableFrom(event.getClass)))
|
if (to.exists(_.isAssignableFrom(event.getClass)))
|
||||||
subscriber ! event
|
subscriber ! event
|
||||||
}
|
}
|
||||||
publishDiff(Gossip.empty, latestGossip, pub)
|
publishDiff(emptyMembershipState, membershipState, pub)
|
||||||
case InitialStateAsSnapshot ⇒
|
case InitialStateAsSnapshot ⇒
|
||||||
sendCurrentClusterState(subscriber)
|
sendCurrentClusterState(subscriber)
|
||||||
}
|
}
|
||||||
|
|
@ -434,22 +442,22 @@ private[cluster] final class ClusterDomainEventPublisher extends Actor with Acto
|
||||||
case Some(c) ⇒ eventStream.unsubscribe(subscriber, c)
|
case Some(c) ⇒ eventStream.unsubscribe(subscriber, c)
|
||||||
}
|
}
|
||||||
|
|
||||||
def publishChanges(newGossip: Gossip): Unit = {
|
def publishChanges(newState: MembershipState): Unit = {
|
||||||
val oldGossip = latestGossip
|
val oldState = membershipState
|
||||||
// keep the latestGossip to be sent to new subscribers
|
// keep the latest state to be sent to new subscribers
|
||||||
latestGossip = newGossip
|
membershipState = newState
|
||||||
publishDiff(oldGossip, newGossip, publish)
|
publishDiff(oldState, newState, publish)
|
||||||
}
|
}
|
||||||
|
|
||||||
def publishDiff(oldGossip: Gossip, newGossip: Gossip, pub: AnyRef ⇒ Unit): Unit = {
|
def publishDiff(oldState: MembershipState, newState: MembershipState, pub: AnyRef ⇒ Unit): Unit = {
|
||||||
diffMemberEvents(oldGossip, newGossip) foreach pub
|
diffMemberEvents(oldState, newState) foreach pub
|
||||||
diffUnreachable(oldGossip, newGossip, selfUniqueAddress) foreach pub
|
diffUnreachable(oldState, newState) foreach pub
|
||||||
diffReachable(oldGossip, newGossip, selfUniqueAddress) foreach pub
|
diffReachable(oldState, newState) foreach pub
|
||||||
diffLeader(selfDc, oldGossip, newGossip, selfUniqueAddress) foreach pub
|
diffLeader(oldState, newState) foreach pub
|
||||||
diffRolesLeader(selfDc, oldGossip, newGossip, selfUniqueAddress) foreach pub
|
diffRolesLeader(oldState, newState) foreach pub
|
||||||
// publish internal SeenState for testing purposes
|
// publish internal SeenState for testing purposes
|
||||||
diffSeen(selfDc, oldGossip, newGossip, selfUniqueAddress) foreach pub
|
diffSeen(oldState, newState) foreach pub
|
||||||
diffReachability(oldGossip, newGossip) foreach pub
|
diffReachability(oldState, newState) foreach pub
|
||||||
}
|
}
|
||||||
|
|
||||||
def publishInternalStats(currentStats: CurrentInternalStats): Unit = publish(currentStats)
|
def publishInternalStats(currentStats: CurrentInternalStats): Unit = publish(currentStats)
|
||||||
|
|
@ -457,6 +465,6 @@ private[cluster] final class ClusterDomainEventPublisher extends Actor with Acto
|
||||||
def publish(event: AnyRef): Unit = eventStream publish event
|
def publish(event: AnyRef): Unit = eventStream publish event
|
||||||
|
|
||||||
def clearState(): Unit = {
|
def clearState(): Unit = {
|
||||||
latestGossip = Gossip.empty
|
membershipState = emptyMembershipState
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,11 +22,6 @@ private[cluster] object Gossip {
|
||||||
def apply(members: immutable.SortedSet[Member]) =
|
def apply(members: immutable.SortedSet[Member]) =
|
||||||
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 convergenceMemberStatus = Set[MemberStatus](Up, Leaving)
|
|
||||||
val convergenceSkipUnreachableWithMemberStatus = Set[MemberStatus](Down, Exiting)
|
|
||||||
val removeUnreachableWithMemberStatus = Set[MemberStatus](Down, Exiting)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -75,7 +70,7 @@ private[cluster] final case class Gossip(
|
||||||
private def assertInvariants(): Unit = {
|
private def assertInvariants(): Unit = {
|
||||||
|
|
||||||
if (members.exists(_.status == Removed))
|
if (members.exists(_.status == Removed))
|
||||||
throw new IllegalArgumentException(s"Live members must have status [${Removed}], " +
|
throw new IllegalArgumentException(s"Live members must not have status [${Removed}], " +
|
||||||
s"got [${members.filter(_.status == Removed)}]")
|
s"got [${members.filter(_.status == Removed)}]")
|
||||||
|
|
||||||
val inReachabilityButNotMember = overview.reachability.allObservers diff members.map(_.uniqueAddress)
|
val inReachabilityButNotMember = overview.reachability.allObservers diff members.map(_.uniqueAddress)
|
||||||
|
|
@ -168,103 +163,17 @@ private[cluster] final case class Gossip(
|
||||||
Gossip(mergedMembers, GossipOverview(mergedSeen, mergedReachability), mergedVClock, mergedTombstones)
|
Gossip(mergedMembers, GossipOverview(mergedSeen, mergedReachability), mergedVClock, mergedTombstones)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if we have a cluster convergence. If there are any in data center node pairs that cannot reach each other
|
|
||||||
* then we can't have a convergence until those nodes reach each other again or one of them is downed
|
|
||||||
*
|
|
||||||
* @return true if convergence have been reached and false if not
|
|
||||||
*/
|
|
||||||
def convergence(dc: DataCenter, selfUniqueAddress: UniqueAddress, exitingConfirmed: Set[UniqueAddress]): Boolean = {
|
|
||||||
// Find cluster members in the data center that are unreachable from other members of the data center
|
|
||||||
// excluding observations from members outside of the data center, that have status DOWN or is passed in as confirmed exiting.
|
|
||||||
val unreachableInDc = dcReachabilityExcludingDownedObservers(dc).allUnreachableOrTerminated.collect {
|
|
||||||
case node if node != selfUniqueAddress && !exitingConfirmed(node) ⇒ member(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If another member in the data center that is UP or LEAVING and has not seen this gossip or is exiting
|
|
||||||
// convergence cannot be reached
|
|
||||||
def memberHinderingConvergenceExists =
|
|
||||||
members.exists(member ⇒
|
|
||||||
member.dataCenter == dc &&
|
|
||||||
Gossip.convergenceMemberStatus(member.status) &&
|
|
||||||
!(seenByNode(member.uniqueAddress) || exitingConfirmed(member.uniqueAddress)))
|
|
||||||
|
|
||||||
// unreachables outside of the data center or with status DOWN or EXITING does not affect convergence
|
|
||||||
def allUnreachablesCanBeIgnored =
|
|
||||||
unreachableInDc.forall(unreachable ⇒ Gossip.convergenceSkipUnreachableWithMemberStatus(unreachable.status))
|
|
||||||
|
|
||||||
allUnreachablesCanBeIgnored && !memberHinderingConvergenceExists
|
|
||||||
}
|
|
||||||
|
|
||||||
lazy val reachabilityExcludingDownedObservers: Reachability = {
|
lazy val reachabilityExcludingDownedObservers: Reachability = {
|
||||||
val downed = members.collect { case m if m.status == Down ⇒ m }
|
val downed = members.collect { case m if m.status == Down ⇒ m }
|
||||||
overview.reachability.removeObservers(downed.map(_.uniqueAddress))
|
overview.reachability.removeObservers(downed.map(_.uniqueAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @return Reachability excluding observations from nodes outside of the data center, but including observed unreachable
|
|
||||||
* nodes outside of the data center
|
|
||||||
*/
|
|
||||||
def dcReachability(dc: DataCenter): Reachability =
|
|
||||||
overview.reachability.removeObservers(members.collect { case m if m.dataCenter != dc ⇒ m.uniqueAddress })
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return reachability for data center nodes, with observations from outside the data center or from downed nodes filtered out
|
|
||||||
*/
|
|
||||||
def dcReachabilityExcludingDownedObservers(dc: DataCenter): Reachability = {
|
|
||||||
val membersToExclude = members.collect { case m if m.status == Down || m.dataCenter != dc ⇒ m.uniqueAddress }
|
|
||||||
overview.reachability.removeObservers(membersToExclude).remove(members.collect { case m if m.dataCenter != dc ⇒ m.uniqueAddress })
|
|
||||||
}
|
|
||||||
|
|
||||||
def dcMembers(dc: DataCenter): SortedSet[Member] =
|
|
||||||
members.filter(_.dataCenter == dc)
|
|
||||||
|
|
||||||
def isDcLeader(dc: DataCenter, node: UniqueAddress, selfUniqueAddress: UniqueAddress): Boolean =
|
|
||||||
dcLeader(dc, selfUniqueAddress).contains(node)
|
|
||||||
|
|
||||||
def dcLeader(dc: DataCenter, selfUniqueAddress: UniqueAddress): Option[UniqueAddress] =
|
|
||||||
leaderOf(dc, members, selfUniqueAddress)
|
|
||||||
|
|
||||||
def roleLeader(dc: DataCenter, role: String, selfUniqueAddress: UniqueAddress): Option[UniqueAddress] =
|
|
||||||
leaderOf(dc, members.filter(_.hasRole(role)), selfUniqueAddress)
|
|
||||||
|
|
||||||
def leaderOf(dc: DataCenter, mbrs: immutable.SortedSet[Member], selfUniqueAddress: UniqueAddress): Option[UniqueAddress] = {
|
|
||||||
val reachability = dcReachability(dc)
|
|
||||||
|
|
||||||
val reachableMembersInDc =
|
|
||||||
if (reachability.isAllReachable) mbrs.filter(m ⇒ m.dataCenter == dc && m.status != Down)
|
|
||||||
else mbrs.filter(m ⇒
|
|
||||||
m.dataCenter == dc &&
|
|
||||||
m.status != Down &&
|
|
||||||
(reachability.isReachable(m.uniqueAddress) || m.uniqueAddress == selfUniqueAddress))
|
|
||||||
if (reachableMembersInDc.isEmpty) None
|
|
||||||
else reachableMembersInDc.find(m ⇒ Gossip.leaderMemberStatus(m.status))
|
|
||||||
.orElse(Some(reachableMembersInDc.min(Member.leaderStatusOrdering)))
|
|
||||||
.map(_.uniqueAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
def allDataCenters: Set[DataCenter] = members.map(_.dataCenter)
|
def allDataCenters: Set[DataCenter] = members.map(_.dataCenter)
|
||||||
|
|
||||||
def allRoles: Set[String] = members.flatMap(_.roles)
|
def allRoles: Set[String] = members.flatMap(_.roles)
|
||||||
|
|
||||||
def isSingletonCluster: Boolean = members.size == 1
|
def isSingletonCluster: Boolean = members.size == 1
|
||||||
|
|
||||||
/**
|
|
||||||
* @return true if toAddress should be reachable from the fromDc in general, within a data center
|
|
||||||
* this means only caring about data center local observations, across data centers it
|
|
||||||
* means caring about all observations for the toAddress.
|
|
||||||
*/
|
|
||||||
def isReachableExcludingDownedObservers(fromDc: DataCenter, toAddress: UniqueAddress): Boolean =
|
|
||||||
if (!hasMember(toAddress)) false
|
|
||||||
else {
|
|
||||||
val to = member(toAddress)
|
|
||||||
|
|
||||||
// if member is in the same data center, we ignore cross data center unreachability
|
|
||||||
if (fromDc == to.dataCenter) dcReachabilityExcludingDownedObservers(fromDc).isReachable(toAddress)
|
|
||||||
// if not it is enough that any non-downed node observed it as unreachable
|
|
||||||
else reachabilityExcludingDownedObservers.isReachable(toAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return true if fromAddress should be able to reach toAddress based on the unreachability data and their
|
* @return true if fromAddress should be able to reach toAddress based on the unreachability data and their
|
||||||
* respective data centers
|
* respective data centers
|
||||||
|
|
|
||||||
|
|
@ -163,7 +163,7 @@ object Member {
|
||||||
if (members.size == 2) acc + members.reduceLeft(highestPriorityOf)
|
if (members.size == 2) acc + members.reduceLeft(highestPriorityOf)
|
||||||
else {
|
else {
|
||||||
val m = members.head
|
val m = members.head
|
||||||
if (tombstones.contains(m.uniqueAddress) || Gossip.removeUnreachableWithMemberStatus(m.status)) acc // removed
|
if (tombstones.contains(m.uniqueAddress) || MembershipState.removeUnreachableWithMemberStatus(m.status)) acc // removed
|
||||||
else acc + m
|
else acc + m
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
122
akka-cluster/src/main/scala/akka/cluster/MembershipState.scala
Normal file
122
akka-cluster/src/main/scala/akka/cluster/MembershipState.scala
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
/**
|
||||||
|
* Copyright (C) 2017 Lightbend Inc. <http://www.lightbend.com>
|
||||||
|
*/
|
||||||
|
package akka.cluster
|
||||||
|
|
||||||
|
import scala.collection.immutable
|
||||||
|
import scala.collection.SortedSet
|
||||||
|
import akka.cluster.ClusterSettings.DataCenter
|
||||||
|
import akka.cluster.MemberStatus._
|
||||||
|
import akka.annotation.InternalApi
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
@InternalApi private[akka] object MembershipState {
|
||||||
|
import MemberStatus._
|
||||||
|
private val leaderMemberStatus = Set[MemberStatus](Up, Leaving)
|
||||||
|
private val convergenceMemberStatus = Set[MemberStatus](Up, Leaving)
|
||||||
|
val convergenceSkipUnreachableWithMemberStatus = Set[MemberStatus](Down, Exiting)
|
||||||
|
val removeUnreachableWithMemberStatus = Set[MemberStatus](Down, Exiting)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* INTERNAL API
|
||||||
|
*/
|
||||||
|
@InternalApi private[akka] final case class MembershipState(latestGossip: Gossip, selfUniqueAddress: UniqueAddress, selfDc: DataCenter) {
|
||||||
|
import MembershipState._
|
||||||
|
|
||||||
|
def members: immutable.SortedSet[Member] = latestGossip.members
|
||||||
|
|
||||||
|
def overview: GossipOverview = latestGossip.overview
|
||||||
|
|
||||||
|
def seen(): MembershipState = copy(latestGossip = latestGossip.seen(selfUniqueAddress))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if we have a cluster convergence. If there are any in data center node pairs that cannot reach each other
|
||||||
|
* then we can't have a convergence until those nodes reach each other again or one of them is downed
|
||||||
|
*
|
||||||
|
* @return true if convergence have been reached and false if not
|
||||||
|
*/
|
||||||
|
def convergence(exitingConfirmed: Set[UniqueAddress]): Boolean = {
|
||||||
|
|
||||||
|
// If another member in the data center that is UP or LEAVING and has not seen this gossip or is exiting
|
||||||
|
// convergence cannot be reached
|
||||||
|
def memberHinderingConvergenceExists =
|
||||||
|
members.exists(member ⇒
|
||||||
|
member.dataCenter == selfDc &&
|
||||||
|
convergenceMemberStatus(member.status) &&
|
||||||
|
!(latestGossip.seenByNode(member.uniqueAddress) || exitingConfirmed(member.uniqueAddress)))
|
||||||
|
|
||||||
|
// Find cluster members in the data center that are unreachable from other members of the data center
|
||||||
|
// excluding observations from members outside of the data center, that have status DOWN or is passed in as confirmed exiting.
|
||||||
|
val unreachableInDc = dcReachabilityExcludingDownedObservers.allUnreachableOrTerminated.collect {
|
||||||
|
case node if node != selfUniqueAddress && !exitingConfirmed(node) ⇒ latestGossip.member(node)
|
||||||
|
}
|
||||||
|
// unreachables outside of the data center or with status DOWN or EXITING does not affect convergence
|
||||||
|
val allUnreachablesCanBeIgnored =
|
||||||
|
unreachableInDc.forall(unreachable ⇒ convergenceSkipUnreachableWithMemberStatus(unreachable.status))
|
||||||
|
|
||||||
|
allUnreachablesCanBeIgnored && !memberHinderingConvergenceExists
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Reachability excluding observations from nodes outside of the data center, but including observed unreachable
|
||||||
|
* nodes outside of the data center
|
||||||
|
*/
|
||||||
|
lazy val dcReachability: Reachability =
|
||||||
|
overview.reachability.removeObservers(
|
||||||
|
members.collect { case m if m.dataCenter != selfDc ⇒ m.uniqueAddress })
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return reachability for data center nodes, with observations from outside the data center or from downed nodes filtered out
|
||||||
|
*/
|
||||||
|
lazy val dcReachabilityExcludingDownedObservers: Reachability = {
|
||||||
|
val membersToExclude = members.collect { case m if m.status == Down || m.dataCenter != selfDc ⇒ m.uniqueAddress }
|
||||||
|
overview.reachability.removeObservers(membersToExclude).remove(members.collect { case m if m.dataCenter != selfDc ⇒ m.uniqueAddress })
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return true if toAddress should be reachable from the fromDc in general, within a data center
|
||||||
|
* this means only caring about data center local observations, across data centers it
|
||||||
|
* means caring about all observations for the toAddress.
|
||||||
|
*/
|
||||||
|
def isReachableExcludingDownedObservers(toAddress: UniqueAddress): Boolean =
|
||||||
|
if (!latestGossip.hasMember(toAddress)) false
|
||||||
|
else {
|
||||||
|
val to = latestGossip.member(toAddress)
|
||||||
|
|
||||||
|
// if member is in the same data center, we ignore cross data center unreachability
|
||||||
|
if (selfDc == to.dataCenter) dcReachabilityExcludingDownedObservers.isReachable(toAddress)
|
||||||
|
// if not it is enough that any non-downed node observed it as unreachable
|
||||||
|
else latestGossip.reachabilityExcludingDownedObservers.isReachable(toAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
def dcMembers: SortedSet[Member] =
|
||||||
|
members.filter(_.dataCenter == selfDc)
|
||||||
|
|
||||||
|
def isLeader(node: UniqueAddress): Boolean =
|
||||||
|
leader.contains(node)
|
||||||
|
|
||||||
|
def leader: Option[UniqueAddress] =
|
||||||
|
leaderOf(members)
|
||||||
|
|
||||||
|
def roleLeader(role: String): Option[UniqueAddress] =
|
||||||
|
leaderOf(members.filter(_.hasRole(role)))
|
||||||
|
|
||||||
|
def leaderOf(mbrs: immutable.SortedSet[Member]): Option[UniqueAddress] = {
|
||||||
|
val reachability = dcReachability
|
||||||
|
|
||||||
|
val reachableMembersInDc =
|
||||||
|
if (reachability.isAllReachable) mbrs.filter(m ⇒ m.dataCenter == selfDc && m.status != Down)
|
||||||
|
else mbrs.filter(m ⇒
|
||||||
|
m.dataCenter == selfDc &&
|
||||||
|
m.status != Down &&
|
||||||
|
(reachability.isReachable(m.uniqueAddress) || m.uniqueAddress == selfUniqueAddress))
|
||||||
|
if (reachableMembersInDc.isEmpty) None
|
||||||
|
else reachableMembersInDc.find(m ⇒ leaderMemberStatus(m.status))
|
||||||
|
.orElse(Some(reachableMembersInDc.min(Member.leaderStatusOrdering)))
|
||||||
|
.map(_.uniqueAddress)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -19,6 +19,7 @@ import akka.testkit.ImplicitSender
|
||||||
import akka.actor.ActorRef
|
import akka.actor.ActorRef
|
||||||
import akka.remote.RARP
|
import akka.remote.RARP
|
||||||
import akka.testkit.TestProbe
|
import akka.testkit.TestProbe
|
||||||
|
import akka.cluster.ClusterSettings.DefaultDataCenter
|
||||||
|
|
||||||
object ClusterDomainEventPublisherSpec {
|
object ClusterDomainEventPublisherSpec {
|
||||||
val config = """
|
val config = """
|
||||||
|
|
@ -36,6 +37,7 @@ class ClusterDomainEventPublisherSpec extends AkkaSpec(ClusterDomainEventPublish
|
||||||
else "akka.tcp"
|
else "akka.tcp"
|
||||||
|
|
||||||
var publisher: ActorRef = _
|
var publisher: ActorRef = _
|
||||||
|
|
||||||
val aUp = TestMember(Address(protocol, "sys", "a", 2552), Up)
|
val aUp = TestMember(Address(protocol, "sys", "a", 2552), Up)
|
||||||
val aLeaving = aUp.copy(status = Leaving)
|
val aLeaving = aUp.copy(status = Leaving)
|
||||||
val aExiting = aLeaving.copy(status = Exiting)
|
val aExiting = aLeaving.copy(status = Exiting)
|
||||||
|
|
@ -48,16 +50,27 @@ class ClusterDomainEventPublisherSpec extends AkkaSpec(ClusterDomainEventPublish
|
||||||
val a51Up = TestMember(Address(protocol, "sys", "a", 2551), Up)
|
val a51Up = TestMember(Address(protocol, "sys", "a", 2551), Up)
|
||||||
val dUp = TestMember(Address(protocol, "sys", "d", 2552), Up, Set("GRP"))
|
val dUp = TestMember(Address(protocol, "sys", "d", 2552), Up, Set("GRP"))
|
||||||
|
|
||||||
|
val emptyMembershipState = MembershipState(Gossip.empty, aUp.uniqueAddress, DefaultDataCenter)
|
||||||
|
|
||||||
val g0 = Gossip(members = SortedSet(aUp)).seen(aUp.uniqueAddress)
|
val g0 = Gossip(members = SortedSet(aUp)).seen(aUp.uniqueAddress)
|
||||||
|
val state0 = MembershipState(g0, aUp.uniqueAddress, DefaultDataCenter)
|
||||||
val g1 = Gossip(members = SortedSet(aUp, cJoining)).seen(aUp.uniqueAddress).seen(cJoining.uniqueAddress)
|
val g1 = Gossip(members = SortedSet(aUp, cJoining)).seen(aUp.uniqueAddress).seen(cJoining.uniqueAddress)
|
||||||
|
val state1 = MembershipState(g1, aUp.uniqueAddress, DefaultDataCenter)
|
||||||
val g2 = Gossip(members = SortedSet(aUp, bExiting, cUp)).seen(aUp.uniqueAddress)
|
val g2 = Gossip(members = SortedSet(aUp, bExiting, cUp)).seen(aUp.uniqueAddress)
|
||||||
|
val state2 = MembershipState(g2, aUp.uniqueAddress, DefaultDataCenter)
|
||||||
val g3 = g2.seen(bExiting.uniqueAddress).seen(cUp.uniqueAddress)
|
val g3 = g2.seen(bExiting.uniqueAddress).seen(cUp.uniqueAddress)
|
||||||
|
val state3 = MembershipState(g3, aUp.uniqueAddress, DefaultDataCenter)
|
||||||
val g4 = Gossip(members = SortedSet(a51Up, aUp, bExiting, cUp)).seen(aUp.uniqueAddress)
|
val g4 = Gossip(members = SortedSet(a51Up, aUp, bExiting, cUp)).seen(aUp.uniqueAddress)
|
||||||
|
val state4 = MembershipState(g4, aUp.uniqueAddress, DefaultDataCenter)
|
||||||
val g5 = Gossip(members = SortedSet(a51Up, aUp, bExiting, cUp)).seen(aUp.uniqueAddress).seen(bExiting.uniqueAddress).seen(cUp.uniqueAddress).seen(a51Up.uniqueAddress)
|
val g5 = Gossip(members = SortedSet(a51Up, aUp, bExiting, cUp)).seen(aUp.uniqueAddress).seen(bExiting.uniqueAddress).seen(cUp.uniqueAddress).seen(a51Up.uniqueAddress)
|
||||||
|
val state5 = MembershipState(g5, aUp.uniqueAddress, DefaultDataCenter)
|
||||||
val g6 = Gossip(members = SortedSet(aLeaving, bExiting, cUp)).seen(aUp.uniqueAddress)
|
val g6 = Gossip(members = SortedSet(aLeaving, bExiting, cUp)).seen(aUp.uniqueAddress)
|
||||||
|
val state6 = MembershipState(g6, aUp.uniqueAddress, DefaultDataCenter)
|
||||||
val g7 = Gossip(members = SortedSet(aExiting, bExiting, cUp)).seen(aUp.uniqueAddress)
|
val g7 = Gossip(members = SortedSet(aExiting, bExiting, cUp)).seen(aUp.uniqueAddress)
|
||||||
|
val state7 = MembershipState(g7, aUp.uniqueAddress, DefaultDataCenter)
|
||||||
val g8 = Gossip(members = SortedSet(aUp, bExiting, cUp, dUp), overview = GossipOverview(reachability =
|
val g8 = Gossip(members = SortedSet(aUp, bExiting, cUp, dUp), overview = GossipOverview(reachability =
|
||||||
Reachability.empty.unreachable(aUp.uniqueAddress, dUp.uniqueAddress))).seen(aUp.uniqueAddress)
|
Reachability.empty.unreachable(aUp.uniqueAddress, dUp.uniqueAddress))).seen(aUp.uniqueAddress)
|
||||||
|
val state8 = MembershipState(g8, aUp.uniqueAddress, DefaultDataCenter)
|
||||||
|
|
||||||
// created in beforeEach
|
// created in beforeEach
|
||||||
var memberSubscriber: TestProbe = _
|
var memberSubscriber: TestProbe = _
|
||||||
|
|
@ -69,7 +82,7 @@ class ClusterDomainEventPublisherSpec extends AkkaSpec(ClusterDomainEventPublish
|
||||||
system.eventStream.subscribe(memberSubscriber.ref, ClusterShuttingDown.getClass)
|
system.eventStream.subscribe(memberSubscriber.ref, ClusterShuttingDown.getClass)
|
||||||
|
|
||||||
publisher = system.actorOf(Props[ClusterDomainEventPublisher])
|
publisher = system.actorOf(Props[ClusterDomainEventPublisher])
|
||||||
publisher ! PublishChanges(g0)
|
publisher ! PublishChanges(state0)
|
||||||
memberSubscriber.expectMsg(MemberUp(aUp))
|
memberSubscriber.expectMsg(MemberUp(aUp))
|
||||||
memberSubscriber.expectMsg(LeaderChanged(Some(aUp.address)))
|
memberSubscriber.expectMsg(LeaderChanged(Some(aUp.address)))
|
||||||
}
|
}
|
||||||
|
|
@ -77,19 +90,19 @@ class ClusterDomainEventPublisherSpec extends AkkaSpec(ClusterDomainEventPublish
|
||||||
"ClusterDomainEventPublisher" must {
|
"ClusterDomainEventPublisher" must {
|
||||||
|
|
||||||
"publish MemberJoined" in {
|
"publish MemberJoined" in {
|
||||||
publisher ! PublishChanges(g1)
|
publisher ! PublishChanges(state1)
|
||||||
memberSubscriber.expectMsg(MemberJoined(cJoining))
|
memberSubscriber.expectMsg(MemberJoined(cJoining))
|
||||||
}
|
}
|
||||||
|
|
||||||
"publish MemberUp" in {
|
"publish MemberUp" in {
|
||||||
publisher ! PublishChanges(g2)
|
publisher ! PublishChanges(state2)
|
||||||
publisher ! PublishChanges(g3)
|
publisher ! PublishChanges(state3)
|
||||||
memberSubscriber.expectMsg(MemberExited(bExiting))
|
memberSubscriber.expectMsg(MemberExited(bExiting))
|
||||||
memberSubscriber.expectMsg(MemberUp(cUp))
|
memberSubscriber.expectMsg(MemberUp(cUp))
|
||||||
}
|
}
|
||||||
|
|
||||||
"publish leader changed" in {
|
"publish leader changed" in {
|
||||||
publisher ! PublishChanges(g4)
|
publisher ! PublishChanges(state4)
|
||||||
memberSubscriber.expectMsg(MemberUp(a51Up))
|
memberSubscriber.expectMsg(MemberUp(a51Up))
|
||||||
memberSubscriber.expectMsg(MemberExited(bExiting))
|
memberSubscriber.expectMsg(MemberExited(bExiting))
|
||||||
memberSubscriber.expectMsg(MemberUp(cUp))
|
memberSubscriber.expectMsg(MemberUp(cUp))
|
||||||
|
|
@ -98,17 +111,17 @@ class ClusterDomainEventPublisherSpec extends AkkaSpec(ClusterDomainEventPublish
|
||||||
}
|
}
|
||||||
|
|
||||||
"publish leader changed when old leader leaves and is removed" in {
|
"publish leader changed when old leader leaves and is removed" in {
|
||||||
publisher ! PublishChanges(g3)
|
publisher ! PublishChanges(state3)
|
||||||
memberSubscriber.expectMsg(MemberExited(bExiting))
|
memberSubscriber.expectMsg(MemberExited(bExiting))
|
||||||
memberSubscriber.expectMsg(MemberUp(cUp))
|
memberSubscriber.expectMsg(MemberUp(cUp))
|
||||||
publisher ! PublishChanges(g6)
|
publisher ! PublishChanges(state6)
|
||||||
memberSubscriber.expectMsg(MemberLeft(aLeaving))
|
memberSubscriber.expectMsg(MemberLeft(aLeaving))
|
||||||
publisher ! PublishChanges(g7)
|
publisher ! PublishChanges(state7)
|
||||||
memberSubscriber.expectMsg(MemberExited(aExiting))
|
memberSubscriber.expectMsg(MemberExited(aExiting))
|
||||||
memberSubscriber.expectMsg(LeaderChanged(Some(cUp.address)))
|
memberSubscriber.expectMsg(LeaderChanged(Some(cUp.address)))
|
||||||
memberSubscriber.expectNoMsg(500 millis)
|
memberSubscriber.expectNoMsg(500 millis)
|
||||||
// at the removed member a an empty gossip is the last thing
|
// at the removed member a an empty gossip is the last thing
|
||||||
publisher ! PublishChanges(Gossip.empty)
|
publisher ! PublishChanges(emptyMembershipState)
|
||||||
memberSubscriber.expectMsg(MemberRemoved(aRemoved, Exiting))
|
memberSubscriber.expectMsg(MemberRemoved(aRemoved, Exiting))
|
||||||
memberSubscriber.expectMsg(MemberRemoved(bRemoved, Exiting))
|
memberSubscriber.expectMsg(MemberRemoved(bRemoved, Exiting))
|
||||||
memberSubscriber.expectMsg(MemberRemoved(cRemoved, Up))
|
memberSubscriber.expectMsg(MemberRemoved(cRemoved, Up))
|
||||||
|
|
@ -116,13 +129,13 @@ class ClusterDomainEventPublisherSpec extends AkkaSpec(ClusterDomainEventPublish
|
||||||
}
|
}
|
||||||
|
|
||||||
"not publish leader changed when same leader" in {
|
"not publish leader changed when same leader" in {
|
||||||
publisher ! PublishChanges(g4)
|
publisher ! PublishChanges(state4)
|
||||||
memberSubscriber.expectMsg(MemberUp(a51Up))
|
memberSubscriber.expectMsg(MemberUp(a51Up))
|
||||||
memberSubscriber.expectMsg(MemberExited(bExiting))
|
memberSubscriber.expectMsg(MemberExited(bExiting))
|
||||||
memberSubscriber.expectMsg(MemberUp(cUp))
|
memberSubscriber.expectMsg(MemberUp(cUp))
|
||||||
memberSubscriber.expectMsg(LeaderChanged(Some(a51Up.address)))
|
memberSubscriber.expectMsg(LeaderChanged(Some(a51Up.address)))
|
||||||
|
|
||||||
publisher ! PublishChanges(g5)
|
publisher ! PublishChanges(state5)
|
||||||
memberSubscriber.expectNoMsg(500 millis)
|
memberSubscriber.expectNoMsg(500 millis)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,12 +143,11 @@ class ClusterDomainEventPublisherSpec extends AkkaSpec(ClusterDomainEventPublish
|
||||||
val subscriber = TestProbe()
|
val subscriber = TestProbe()
|
||||||
publisher ! Subscribe(subscriber.ref, InitialStateAsSnapshot, Set(classOf[RoleLeaderChanged]))
|
publisher ! Subscribe(subscriber.ref, InitialStateAsSnapshot, Set(classOf[RoleLeaderChanged]))
|
||||||
subscriber.expectMsgType[CurrentClusterState]
|
subscriber.expectMsgType[CurrentClusterState]
|
||||||
publisher ! PublishChanges(Gossip(members = SortedSet(cJoining, dUp)))
|
publisher ! PublishChanges(MembershipState(Gossip(members = SortedSet(cJoining, dUp)), dUp.uniqueAddress, DefaultDataCenter))
|
||||||
subscriber.expectMsgAllOf(
|
subscriber.expectMsgAllOf(
|
||||||
RoleLeaderChanged("GRP", Some(dUp.address)),
|
RoleLeaderChanged("GRP", Some(dUp.address)),
|
||||||
RoleLeaderChanged(ClusterSettings.DcRolePrefix + ClusterSettings.DefaultDataCenter, Some(dUp.address))
|
RoleLeaderChanged(ClusterSettings.DcRolePrefix + ClusterSettings.DefaultDataCenter, Some(dUp.address)))
|
||||||
)
|
publisher ! PublishChanges(MembershipState(Gossip(members = SortedSet(cUp, dUp)), dUp.uniqueAddress, DefaultDataCenter))
|
||||||
publisher ! PublishChanges(Gossip(members = SortedSet(cUp, dUp)))
|
|
||||||
subscriber.expectMsg(RoleLeaderChanged("GRP", Some(cUp.address)))
|
subscriber.expectMsg(RoleLeaderChanged("GRP", Some(cUp.address)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -150,7 +162,7 @@ class ClusterDomainEventPublisherSpec extends AkkaSpec(ClusterDomainEventPublish
|
||||||
|
|
||||||
"send events corresponding to current state when subscribe" in {
|
"send events corresponding to current state when subscribe" in {
|
||||||
val subscriber = TestProbe()
|
val subscriber = TestProbe()
|
||||||
publisher ! PublishChanges(g8)
|
publisher ! PublishChanges(state8)
|
||||||
publisher ! Subscribe(subscriber.ref, InitialStateAsEvents, Set(classOf[MemberEvent], classOf[ReachabilityEvent]))
|
publisher ! Subscribe(subscriber.ref, InitialStateAsEvents, Set(classOf[MemberEvent], classOf[ReachabilityEvent]))
|
||||||
subscriber.receiveN(4).toSet should be(Set(MemberUp(aUp), MemberUp(cUp), MemberUp(dUp), MemberExited(bExiting)))
|
subscriber.receiveN(4).toSet should be(Set(MemberUp(aUp), MemberUp(cUp), MemberUp(dUp), MemberExited(bExiting)))
|
||||||
subscriber.expectMsg(UnreachableMember(dUp))
|
subscriber.expectMsg(UnreachableMember(dUp))
|
||||||
|
|
@ -162,7 +174,7 @@ class ClusterDomainEventPublisherSpec extends AkkaSpec(ClusterDomainEventPublish
|
||||||
publisher ! Subscribe(subscriber.ref, InitialStateAsSnapshot, Set(classOf[MemberEvent]))
|
publisher ! Subscribe(subscriber.ref, InitialStateAsSnapshot, Set(classOf[MemberEvent]))
|
||||||
subscriber.expectMsgType[CurrentClusterState]
|
subscriber.expectMsgType[CurrentClusterState]
|
||||||
publisher ! Unsubscribe(subscriber.ref, Some(classOf[MemberEvent]))
|
publisher ! Unsubscribe(subscriber.ref, Some(classOf[MemberEvent]))
|
||||||
publisher ! PublishChanges(g3)
|
publisher ! PublishChanges(state3)
|
||||||
subscriber.expectNoMsg(500 millis)
|
subscriber.expectNoMsg(500 millis)
|
||||||
// but memberSubscriber is still subscriber
|
// but memberSubscriber is still subscriber
|
||||||
memberSubscriber.expectMsg(MemberExited(bExiting))
|
memberSubscriber.expectMsg(MemberExited(bExiting))
|
||||||
|
|
@ -173,10 +185,10 @@ class ClusterDomainEventPublisherSpec extends AkkaSpec(ClusterDomainEventPublish
|
||||||
val subscriber = TestProbe()
|
val subscriber = TestProbe()
|
||||||
publisher ! Subscribe(subscriber.ref, InitialStateAsSnapshot, Set(classOf[SeenChanged]))
|
publisher ! Subscribe(subscriber.ref, InitialStateAsSnapshot, Set(classOf[SeenChanged]))
|
||||||
subscriber.expectMsgType[CurrentClusterState]
|
subscriber.expectMsgType[CurrentClusterState]
|
||||||
publisher ! PublishChanges(g2)
|
publisher ! PublishChanges(state2)
|
||||||
subscriber.expectMsgType[SeenChanged]
|
subscriber.expectMsgType[SeenChanged]
|
||||||
subscriber.expectNoMsg(500 millis)
|
subscriber.expectNoMsg(500 millis)
|
||||||
publisher ! PublishChanges(g3)
|
publisher ! PublishChanges(state3)
|
||||||
subscriber.expectMsgType[SeenChanged]
|
subscriber.expectMsgType[SeenChanged]
|
||||||
subscriber.expectNoMsg(500 millis)
|
subscriber.expectNoMsg(500 millis)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,30 +38,33 @@ class ClusterDomainEventSpec extends WordSpec with Matchers {
|
||||||
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) }
|
||||||
|
|
||||||
|
private def state(g: Gossip): MembershipState =
|
||||||
|
MembershipState(g, selfDummyAddress, ClusterSettings.DefaultDataCenter)
|
||||||
|
|
||||||
"Domain events" must {
|
"Domain events" must {
|
||||||
|
|
||||||
"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, selfDummyAddress) should ===(Seq.empty)
|
diffUnreachable(state(g1), state(g1)) should ===(Seq.empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
"be produced for new members" in {
|
"be produced for new members" in {
|
||||||
val (g1, _) = converge(Gossip(members = SortedSet(aUp)))
|
val (g1, _) = converge(Gossip(members = SortedSet(aUp)))
|
||||||
val (g2, s2) = converge(Gossip(members = SortedSet(aUp, bUp, eJoining)))
|
val (g2, s2) = converge(Gossip(members = SortedSet(aUp, bUp, eJoining)))
|
||||||
|
|
||||||
diffMemberEvents(g1, g2) should ===(Seq(MemberUp(bUp), MemberJoined(eJoining)))
|
diffMemberEvents(state(g1), state(g2)) should ===(Seq(MemberUp(bUp), MemberJoined(eJoining)))
|
||||||
diffUnreachable(g1, g2, selfDummyAddress) should ===(Seq.empty)
|
diffUnreachable(state(g1), state(g2)) should ===(Seq.empty)
|
||||||
diffSeen(ClusterSettings.DefaultDataCenter, g1, g2, selfDummyAddress) should ===(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address))))
|
diffSeen(state(g1), state(g2)) should ===(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address))))
|
||||||
}
|
}
|
||||||
|
|
||||||
"be produced for changed status of members" in {
|
"be produced for changed status of members" in {
|
||||||
val (g1, _) = converge(Gossip(members = SortedSet(aJoining, bUp, cUp)))
|
val (g1, _) = converge(Gossip(members = SortedSet(aJoining, bUp, cUp)))
|
||||||
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 ===(Seq(MemberUp(aUp), MemberLeft(cLeaving), MemberJoined(eJoining)))
|
diffMemberEvents(state(g1), state(g2)) should ===(Seq(MemberUp(aUp), MemberLeft(cLeaving), MemberJoined(eJoining)))
|
||||||
diffUnreachable(g1, g2, selfDummyAddress) should ===(Seq.empty)
|
diffUnreachable(state(g1), state(g2)) should ===(Seq.empty)
|
||||||
diffSeen(ClusterSettings.DefaultDataCenter, g1, g2, selfDummyAddress) should ===(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address))))
|
diffSeen(state(g1), state(g2)) should ===(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address))))
|
||||||
}
|
}
|
||||||
|
|
||||||
"be produced for members in unreachable" in {
|
"be produced for members in unreachable" in {
|
||||||
|
|
@ -73,10 +76,13 @@ 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, selfDummyAddress) should ===(Seq(UnreachableMember(bDown)))
|
diffUnreachable(state(g1), state(g2)) should ===(Seq(UnreachableMember(bDown)))
|
||||||
// never include self member in unreachable
|
// never include self member in unreachable
|
||||||
diffUnreachable(g1, g2, bDown.uniqueAddress) should ===(Seq())
|
|
||||||
diffSeen(ClusterSettings.DefaultDataCenter, g1, g2, selfDummyAddress) should ===(Seq.empty)
|
diffUnreachable(
|
||||||
|
MembershipState(g1, bDown.uniqueAddress, ClusterSettings.DefaultDataCenter),
|
||||||
|
MembershipState(g2, bDown.uniqueAddress, ClusterSettings.DefaultDataCenter)) should ===(Seq())
|
||||||
|
diffSeen(state(g1), state(g2)) should ===(Seq.empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
"be produced for members becoming reachable after unreachable" in {
|
"be produced for members becoming reachable after unreachable" in {
|
||||||
|
|
@ -90,50 +96,54 @@ 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, selfDummyAddress) should ===(Seq(UnreachableMember(cUp)))
|
diffUnreachable(state(g1), state(g2)) should ===(Seq(UnreachableMember(cUp)))
|
||||||
// never include self member in unreachable
|
// never include self member in unreachable
|
||||||
diffUnreachable(g1, g2, cUp.uniqueAddress) should ===(Seq())
|
diffUnreachable(
|
||||||
diffReachable(g1, g2, selfDummyAddress) should ===(Seq(ReachableMember(bUp)))
|
MembershipState(g1, cUp.uniqueAddress, ClusterSettings.DefaultDataCenter),
|
||||||
|
MembershipState(g2, cUp.uniqueAddress, ClusterSettings.DefaultDataCenter)) should ===(Seq())
|
||||||
|
diffReachable(state(g1), state(g2)) should ===(Seq(ReachableMember(bUp)))
|
||||||
// never include self member in reachable
|
// never include self member in reachable
|
||||||
diffReachable(g1, g2, bUp.uniqueAddress) should ===(Seq())
|
diffReachable(
|
||||||
|
MembershipState(g1, bUp.uniqueAddress, ClusterSettings.DefaultDataCenter),
|
||||||
|
MembershipState(g2, bUp.uniqueAddress, ClusterSettings.DefaultDataCenter)) should ===(Seq())
|
||||||
}
|
}
|
||||||
|
|
||||||
"be produced for removed members" in {
|
"be produced for removed members" in {
|
||||||
val (g1, _) = converge(Gossip(members = SortedSet(aUp, dExiting)))
|
val (g1, _) = converge(Gossip(members = SortedSet(aUp, dExiting)))
|
||||||
val (g2, s2) = converge(Gossip(members = SortedSet(aUp)))
|
val (g2, s2) = converge(Gossip(members = SortedSet(aUp)))
|
||||||
|
|
||||||
diffMemberEvents(g1, g2) should ===(Seq(MemberRemoved(dRemoved, Exiting)))
|
diffMemberEvents(state(g1), state(g2)) should ===(Seq(MemberRemoved(dRemoved, Exiting)))
|
||||||
diffUnreachable(g1, g2, selfDummyAddress) should ===(Seq.empty)
|
diffUnreachable(state(g1), state(g2)) should ===(Seq.empty)
|
||||||
diffSeen(ClusterSettings.DefaultDataCenter, g1, g2, selfDummyAddress) should ===(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address))))
|
diffSeen(state(g1), state(g2)) should ===(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address))))
|
||||||
}
|
}
|
||||||
|
|
||||||
"be produced for convergence changes" in {
|
"be produced for convergence changes" in {
|
||||||
val g1 = Gossip(members = SortedSet(aUp, bUp, eJoining)).seen(aUp.uniqueAddress).seen(bUp.uniqueAddress).seen(eJoining.uniqueAddress)
|
val g1 = Gossip(members = SortedSet(aUp, bUp, eJoining)).seen(aUp.uniqueAddress).seen(bUp.uniqueAddress).seen(eJoining.uniqueAddress)
|
||||||
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 ===(Seq.empty)
|
diffMemberEvents(state(g1), state(g2)) should ===(Seq.empty)
|
||||||
diffUnreachable(g1, g2, selfDummyAddress) should ===(Seq.empty)
|
diffUnreachable(state(g1), state(g2)) should ===(Seq.empty)
|
||||||
diffSeen(ClusterSettings.DefaultDataCenter, g1, g2, selfDummyAddress) should ===(Seq(SeenChanged(convergence = true, seenBy = Set(aUp.address, bUp.address))))
|
diffSeen(state(g1), state(g2)) should ===(Seq(SeenChanged(convergence = true, seenBy = Set(aUp.address, bUp.address))))
|
||||||
diffMemberEvents(g2, g1) should ===(Seq.empty)
|
diffMemberEvents(state(g2), state(g1)) should ===(Seq.empty)
|
||||||
diffUnreachable(g2, g1, selfDummyAddress) should ===(Seq.empty)
|
diffUnreachable(state(g2), state(g1)) should ===(Seq.empty)
|
||||||
diffSeen(ClusterSettings.DefaultDataCenter, g2, g1, selfDummyAddress) should ===(Seq(SeenChanged(convergence = true, seenBy = Set(aUp.address, bUp.address, eJoining.address))))
|
diffSeen(state(g2), state(g1)) should ===(Seq(SeenChanged(convergence = true, seenBy = Set(aUp.address, bUp.address, eJoining.address))))
|
||||||
}
|
}
|
||||||
|
|
||||||
"be produced for leader changes" in {
|
"be produced for leader changes" in {
|
||||||
val (g1, _) = converge(Gossip(members = SortedSet(aUp, bUp, eJoining)))
|
val (g1, _) = converge(Gossip(members = SortedSet(aUp, bUp, eJoining)))
|
||||||
val (g2, s2) = converge(Gossip(members = SortedSet(bUp, eJoining)))
|
val (g2, s2) = converge(Gossip(members = SortedSet(bUp, eJoining)))
|
||||||
|
|
||||||
diffMemberEvents(g1, g2) should ===(Seq(MemberRemoved(aRemoved, Up)))
|
diffMemberEvents(state(g1), state(g2)) should ===(Seq(MemberRemoved(aRemoved, Up)))
|
||||||
diffUnreachable(g1, g2, selfDummyAddress) should ===(Seq.empty)
|
diffUnreachable(state(g1), state(g2)) should ===(Seq.empty)
|
||||||
diffSeen(ClusterSettings.DefaultDataCenter, g1, g2, selfDummyAddress) should ===(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address))))
|
diffSeen(state(g1), state(g2)) should ===(Seq(SeenChanged(convergence = true, seenBy = s2.map(_.address))))
|
||||||
diffLeader(ClusterSettings.DefaultDataCenter, g1, g2, selfDummyAddress) should ===(Seq(LeaderChanged(Some(bUp.address))))
|
diffLeader(state(g1), state(g2)) should ===(Seq(LeaderChanged(Some(bUp.address))))
|
||||||
}
|
}
|
||||||
|
|
||||||
"be produced for role leader changes in the same data center" in {
|
"be produced for role leader changes in the same data center" in {
|
||||||
val g0 = Gossip.empty
|
val g0 = Gossip.empty
|
||||||
val g1 = Gossip(members = SortedSet(aUp, bUp, cUp, dLeaving, eJoining))
|
val g1 = Gossip(members = SortedSet(aUp, bUp, cUp, dLeaving, eJoining))
|
||||||
val g2 = Gossip(members = SortedSet(bUp, cUp, dExiting, eJoining))
|
val g2 = Gossip(members = SortedSet(bUp, cUp, dExiting, eJoining))
|
||||||
diffRolesLeader(ClusterSettings.DefaultDataCenter, g0, g1, selfDummyAddress) should ===(
|
diffRolesLeader(state(g0), state(g1)) should ===(
|
||||||
Set(
|
Set(
|
||||||
// since this role is implicitly added
|
// since this role is implicitly added
|
||||||
RoleLeaderChanged(ClusterSettings.DcRolePrefix + ClusterSettings.DefaultDataCenter, Some(aUp.address)),
|
RoleLeaderChanged(ClusterSettings.DcRolePrefix + ClusterSettings.DefaultDataCenter, Some(aUp.address)),
|
||||||
|
|
@ -143,7 +153,7 @@ class ClusterDomainEventSpec extends WordSpec with Matchers {
|
||||||
RoleLeaderChanged("DD", Some(dLeaving.address)),
|
RoleLeaderChanged("DD", Some(dLeaving.address)),
|
||||||
RoleLeaderChanged("DE", Some(dLeaving.address)),
|
RoleLeaderChanged("DE", Some(dLeaving.address)),
|
||||||
RoleLeaderChanged("EE", Some(eUp.address))))
|
RoleLeaderChanged("EE", Some(eUp.address))))
|
||||||
diffRolesLeader(ClusterSettings.DefaultDataCenter, g1, g2, selfDummyAddress) should ===(
|
diffRolesLeader(state(g1), state(g2)) should ===(
|
||||||
Set(
|
Set(
|
||||||
RoleLeaderChanged(ClusterSettings.DcRolePrefix + ClusterSettings.DefaultDataCenter, Some(bUp.address)),
|
RoleLeaderChanged(ClusterSettings.DcRolePrefix + ClusterSettings.DefaultDataCenter, Some(bUp.address)),
|
||||||
RoleLeaderChanged("AA", None),
|
RoleLeaderChanged("AA", None),
|
||||||
|
|
@ -153,10 +163,14 @@ class ClusterDomainEventSpec extends WordSpec with Matchers {
|
||||||
|
|
||||||
"not be produced for role leader changes in other data centers" in {
|
"not be produced for role leader changes in other data centers" in {
|
||||||
val g0 = Gossip.empty
|
val g0 = Gossip.empty
|
||||||
|
val s0 = state(g0).copy(selfDc = "dc2")
|
||||||
val g1 = Gossip(members = SortedSet(aUp, bUp, cUp, dLeaving, eJoining))
|
val g1 = Gossip(members = SortedSet(aUp, bUp, cUp, dLeaving, eJoining))
|
||||||
|
val s1 = state(g1).copy(selfDc = "dc2")
|
||||||
val g2 = Gossip(members = SortedSet(bUp, cUp, dExiting, eJoining))
|
val g2 = Gossip(members = SortedSet(bUp, cUp, dExiting, eJoining))
|
||||||
diffRolesLeader("dc2", g0, g1, selfDummyAddress) should ===(Set.empty)
|
val s2 = state(g2).copy(selfDc = "dc2")
|
||||||
diffRolesLeader("dc2", g1, g2, selfDummyAddress) should ===(Set.empty)
|
|
||||||
|
diffRolesLeader(s0, s1) should ===(Set.empty)
|
||||||
|
diffRolesLeader(s1, s2) should ===(Set.empty)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ package akka.cluster
|
||||||
import org.scalatest.WordSpec
|
import org.scalatest.WordSpec
|
||||||
import org.scalatest.Matchers
|
import org.scalatest.Matchers
|
||||||
import akka.actor.Address
|
import akka.actor.Address
|
||||||
|
import akka.cluster.ClusterSettings.DataCenter
|
||||||
import akka.cluster.ClusterSettings.DefaultDataCenter
|
import akka.cluster.ClusterSettings.DefaultDataCenter
|
||||||
|
|
||||||
import scala.collection.immutable.SortedSet
|
import scala.collection.immutable.SortedSet
|
||||||
|
|
@ -33,6 +34,9 @@ class GossipSpec extends WordSpec with Matchers {
|
||||||
val dc2d1 = TestMember(Address("akka.tcp", "sys", "d", 2552), Up, Set.empty, dataCenter = "dc2")
|
val dc2d1 = TestMember(Address("akka.tcp", "sys", "d", 2552), Up, Set.empty, dataCenter = "dc2")
|
||||||
val dc2d2 = TestMember(dc2d1.address, status = Down, roles = Set.empty, dataCenter = dc2d1.dataCenter)
|
val dc2d2 = TestMember(dc2d1.address, status = Down, roles = Set.empty, dataCenter = dc2d1.dataCenter)
|
||||||
|
|
||||||
|
private def state(g: Gossip, selfMember: Member = a1): MembershipState =
|
||||||
|
MembershipState(g, selfMember.uniqueAddress, selfMember.dataCenter)
|
||||||
|
|
||||||
"A Gossip" must {
|
"A Gossip" must {
|
||||||
|
|
||||||
"have correct test setup" in {
|
"have correct test setup" in {
|
||||||
|
|
@ -41,40 +45,40 @@ class GossipSpec extends WordSpec with Matchers {
|
||||||
}
|
}
|
||||||
|
|
||||||
"reach convergence when it's empty" in {
|
"reach convergence when it's empty" in {
|
||||||
Gossip.empty.convergence(DefaultDataCenter, a1.uniqueAddress, Set.empty) should ===(true)
|
state(Gossip.empty).convergence(Set.empty) should ===(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"reach convergence for one node" in {
|
"reach convergence for one node" in {
|
||||||
val g1 = Gossip(members = SortedSet(a1)).seen(a1.uniqueAddress)
|
val g1 = Gossip(members = SortedSet(a1)).seen(a1.uniqueAddress)
|
||||||
g1.convergence(DefaultDataCenter, a1.uniqueAddress, Set.empty) should ===(true)
|
state(g1).convergence(Set.empty) should ===(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"not reach convergence until all have seen version" in {
|
"not reach convergence until all have seen version" in {
|
||||||
val g1 = Gossip(members = SortedSet(a1, b1)).seen(a1.uniqueAddress)
|
val g1 = Gossip(members = SortedSet(a1, b1)).seen(a1.uniqueAddress)
|
||||||
g1.convergence(DefaultDataCenter, a1.uniqueAddress, Set.empty) should ===(false)
|
state(g1).convergence(Set.empty) should ===(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
"reach convergence for two nodes" in {
|
"reach convergence for two nodes" in {
|
||||||
val g1 = Gossip(members = SortedSet(a1, b1)).seen(a1.uniqueAddress).seen(b1.uniqueAddress)
|
val g1 = Gossip(members = SortedSet(a1, b1)).seen(a1.uniqueAddress).seen(b1.uniqueAddress)
|
||||||
g1.convergence(DefaultDataCenter, a1.uniqueAddress, Set.empty) should ===(true)
|
state(g1).convergence(Set.empty) should ===(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"reach convergence, skipping joining" in {
|
"reach convergence, skipping joining" in {
|
||||||
// e1 is joining
|
// e1 is joining
|
||||||
val g1 = Gossip(members = SortedSet(a1, b1, e1)).seen(a1.uniqueAddress).seen(b1.uniqueAddress)
|
val g1 = Gossip(members = SortedSet(a1, b1, e1)).seen(a1.uniqueAddress).seen(b1.uniqueAddress)
|
||||||
g1.convergence(DefaultDataCenter, a1.uniqueAddress, Set.empty) should ===(true)
|
state(g1).convergence(Set.empty) should ===(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"reach convergence, skipping down" in {
|
"reach convergence, skipping down" in {
|
||||||
// e3 is down
|
// e3 is down
|
||||||
val g1 = Gossip(members = SortedSet(a1, b1, e3)).seen(a1.uniqueAddress).seen(b1.uniqueAddress)
|
val g1 = Gossip(members = SortedSet(a1, b1, e3)).seen(a1.uniqueAddress).seen(b1.uniqueAddress)
|
||||||
g1.convergence(DefaultDataCenter, a1.uniqueAddress, Set.empty) should ===(true)
|
state(g1).convergence(Set.empty) should ===(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"reach convergence, skipping Leaving with exitingConfirmed" in {
|
"reach convergence, skipping Leaving with exitingConfirmed" in {
|
||||||
// c1 is Leaving
|
// c1 is Leaving
|
||||||
val g1 = Gossip(members = SortedSet(a1, b1, c1)).seen(a1.uniqueAddress).seen(b1.uniqueAddress)
|
val g1 = Gossip(members = SortedSet(a1, b1, c1)).seen(a1.uniqueAddress).seen(b1.uniqueAddress)
|
||||||
g1.convergence(DefaultDataCenter, a1.uniqueAddress, Set(c1.uniqueAddress)) should ===(true)
|
state(g1).convergence(Set(c1.uniqueAddress)) should ===(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"reach convergence, skipping unreachable Leaving with exitingConfirmed" in {
|
"reach convergence, skipping unreachable Leaving with exitingConfirmed" in {
|
||||||
|
|
@ -82,16 +86,16 @@ class GossipSpec extends WordSpec with Matchers {
|
||||||
val r1 = Reachability.empty.unreachable(b1.uniqueAddress, c1.uniqueAddress)
|
val r1 = Reachability.empty.unreachable(b1.uniqueAddress, c1.uniqueAddress)
|
||||||
val g1 = Gossip(members = SortedSet(a1, b1, c1), overview = GossipOverview(reachability = r1))
|
val g1 = Gossip(members = SortedSet(a1, b1, c1), overview = GossipOverview(reachability = r1))
|
||||||
.seen(a1.uniqueAddress).seen(b1.uniqueAddress)
|
.seen(a1.uniqueAddress).seen(b1.uniqueAddress)
|
||||||
g1.convergence(DefaultDataCenter, a1.uniqueAddress, Set(c1.uniqueAddress)) should ===(true)
|
state(g1).convergence(Set(c1.uniqueAddress)) should ===(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"not reach convergence when unreachable" in {
|
"not reach convergence when unreachable" in {
|
||||||
val r1 = Reachability.empty.unreachable(b1.uniqueAddress, a1.uniqueAddress)
|
val r1 = Reachability.empty.unreachable(b1.uniqueAddress, a1.uniqueAddress)
|
||||||
val g1 = (Gossip(members = SortedSet(a1, b1), overview = GossipOverview(reachability = r1)))
|
val g1 = (Gossip(members = SortedSet(a1, b1), overview = GossipOverview(reachability = r1)))
|
||||||
.seen(a1.uniqueAddress).seen(b1.uniqueAddress)
|
.seen(a1.uniqueAddress).seen(b1.uniqueAddress)
|
||||||
g1.convergence(DefaultDataCenter, b1.uniqueAddress, Set.empty) should ===(false)
|
state(g1, b1).convergence(Set.empty) should ===(false)
|
||||||
// but from a1's point of view (it knows that itself is not unreachable)
|
// but from a1's point of view (it knows that itself is not unreachable)
|
||||||
g1.convergence(DefaultDataCenter, a1.uniqueAddress, Set.empty) should ===(true)
|
state(g1).convergence(Set.empty) should ===(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"reach convergence when downed node has observed unreachable" in {
|
"reach convergence when downed node has observed unreachable" in {
|
||||||
|
|
@ -99,7 +103,7 @@ class GossipSpec extends WordSpec with Matchers {
|
||||||
val r1 = Reachability.empty.unreachable(e3.uniqueAddress, a1.uniqueAddress)
|
val r1 = Reachability.empty.unreachable(e3.uniqueAddress, a1.uniqueAddress)
|
||||||
val g1 = (Gossip(members = SortedSet(a1, b1, e3), overview = GossipOverview(reachability = r1)))
|
val g1 = (Gossip(members = SortedSet(a1, b1, e3), overview = GossipOverview(reachability = r1)))
|
||||||
.seen(a1.uniqueAddress).seen(b1.uniqueAddress).seen(e3.uniqueAddress)
|
.seen(a1.uniqueAddress).seen(b1.uniqueAddress).seen(e3.uniqueAddress)
|
||||||
g1.convergence(DefaultDataCenter, b1.uniqueAddress, Set.empty) should ===(true)
|
state(g1, b1).convergence(Set.empty) should ===(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"merge members by status priority" in {
|
"merge members by status priority" in {
|
||||||
|
|
@ -146,37 +150,33 @@ class GossipSpec extends WordSpec with Matchers {
|
||||||
}
|
}
|
||||||
|
|
||||||
"have leader as first member based on ordering, except Exiting status" in {
|
"have leader as first member based on ordering, except Exiting status" in {
|
||||||
Gossip(members = SortedSet(c2, e2)).dcLeader(DefaultDataCenter, c2.uniqueAddress) should ===(Some(c2.uniqueAddress))
|
state(Gossip(members = SortedSet(c2, e2)), c2).leader should ===(Some(c2.uniqueAddress))
|
||||||
Gossip(members = SortedSet(c3, e2)).dcLeader(DefaultDataCenter, c3.uniqueAddress) should ===(Some(e2.uniqueAddress))
|
state(Gossip(members = SortedSet(c3, e2)), c3).leader should ===(Some(e2.uniqueAddress))
|
||||||
Gossip(members = SortedSet(c3)).dcLeader(DefaultDataCenter, c3.uniqueAddress) should ===(Some(c3.uniqueAddress))
|
state(Gossip(members = SortedSet(c3)), c3).leader should ===(Some(c3.uniqueAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
"have leader as first reachable member based on ordering" in {
|
"have leader as first reachable member based on ordering" in {
|
||||||
val r1 = Reachability.empty.unreachable(e2.uniqueAddress, c2.uniqueAddress)
|
val r1 = Reachability.empty.unreachable(e2.uniqueAddress, c2.uniqueAddress)
|
||||||
val g1 = Gossip(members = SortedSet(c2, e2), overview = GossipOverview(reachability = r1))
|
val g1 = Gossip(members = SortedSet(c2, e2), overview = GossipOverview(reachability = r1))
|
||||||
g1.dcLeader(DefaultDataCenter, e2.uniqueAddress) should ===(Some(e2.uniqueAddress))
|
state(g1, e2).leader should ===(Some(e2.uniqueAddress))
|
||||||
// but when c2 is selfUniqueAddress
|
// but when c2 is selfUniqueAddress
|
||||||
g1.dcLeader(DefaultDataCenter, c2.uniqueAddress) should ===(Some(c2.uniqueAddress))
|
state(g1, c2).leader should ===(Some(c2.uniqueAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
"not have Down member as leader" in {
|
"not have Down member as leader" in {
|
||||||
Gossip(members = SortedSet(e3)).dcLeader(DefaultDataCenter, e3.uniqueAddress) should ===(None)
|
state(Gossip(members = SortedSet(e3)), e3).leader should ===(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
"have a leader per data center" in {
|
"have a leader per data center" in {
|
||||||
val g1 = Gossip(members = SortedSet(dc1a1, dc1b1, dc2c1, dc2d1))
|
val g1 = Gossip(members = SortedSet(dc1a1, dc1b1, dc2c1, dc2d1))
|
||||||
|
|
||||||
// everybodys point of view is dc1a1 being leader of dc1
|
// dc1a1 being leader of dc1
|
||||||
g1.dcLeader("dc1", dc1a1.uniqueAddress) should ===(Some(dc1a1.uniqueAddress))
|
state(g1, dc1a1).leader should ===(Some(dc1a1.uniqueAddress))
|
||||||
g1.dcLeader("dc1", dc1b1.uniqueAddress) should ===(Some(dc1a1.uniqueAddress))
|
state(g1, dc1b1).leader should ===(Some(dc1a1.uniqueAddress))
|
||||||
g1.dcLeader("dc1", dc2c1.uniqueAddress) should ===(Some(dc1a1.uniqueAddress))
|
|
||||||
g1.dcLeader("dc1", dc2d1.uniqueAddress) should ===(Some(dc1a1.uniqueAddress))
|
|
||||||
|
|
||||||
// and dc2c1 being leader of dc2
|
// and dc2c1 being leader of dc2
|
||||||
g1.dcLeader("dc2", dc1a1.uniqueAddress) should ===(Some(dc2c1.uniqueAddress))
|
state(g1, dc2c1).leader should ===(Some(dc2c1.uniqueAddress))
|
||||||
g1.dcLeader("dc2", dc1b1.uniqueAddress) should ===(Some(dc2c1.uniqueAddress))
|
state(g1, dc2d1).leader should ===(Some(dc2c1.uniqueAddress))
|
||||||
g1.dcLeader("dc2", dc2c1.uniqueAddress) should ===(Some(dc2c1.uniqueAddress))
|
|
||||||
g1.dcLeader("dc2", dc2d1.uniqueAddress) should ===(Some(dc2c1.uniqueAddress))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"merge seen table correctly" in {
|
"merge seen table correctly" in {
|
||||||
|
|
@ -218,11 +218,11 @@ class GossipSpec extends WordSpec with Matchers {
|
||||||
.seen(dc1b1.uniqueAddress)
|
.seen(dc1b1.uniqueAddress)
|
||||||
.seen(dc2c1.uniqueAddress)
|
.seen(dc2c1.uniqueAddress)
|
||||||
.seen(dc2d1.uniqueAddress)
|
.seen(dc2d1.uniqueAddress)
|
||||||
g.dcLeader("dc1", dc1a1.uniqueAddress) should ===(Some(dc1a1.uniqueAddress))
|
state(g, dc1a1).leader should ===(Some(dc1a1.uniqueAddress))
|
||||||
g.convergence("dc1", dc1a1.uniqueAddress, Set.empty) should ===(true)
|
state(g, dc1a1).convergence(Set.empty) should ===(true)
|
||||||
|
|
||||||
g.dcLeader("dc2", dc2c1.uniqueAddress) should ===(Some(dc2c1.uniqueAddress))
|
state(g, dc2c1).leader should ===(Some(dc2c1.uniqueAddress))
|
||||||
g.convergence("dc2", dc2c1.uniqueAddress, Set.empty) should ===(true)
|
state(g, dc2c1).convergence(Set.empty) should ===(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"reach convergence per data center even if members of another data center has not seen the gossip" in {
|
"reach convergence per data center even if members of another data center has not seen the gossip" in {
|
||||||
|
|
@ -233,12 +233,12 @@ class GossipSpec extends WordSpec with Matchers {
|
||||||
// dc2d1 has not seen the gossip
|
// dc2d1 has not seen the gossip
|
||||||
|
|
||||||
// so dc1 can reach convergence
|
// so dc1 can reach convergence
|
||||||
g.dcLeader("dc1", dc1a1.uniqueAddress) should ===(Some(dc1a1.uniqueAddress))
|
state(g, dc1a1).leader should ===(Some(dc1a1.uniqueAddress))
|
||||||
g.convergence("dc1", dc1a1.uniqueAddress, Set.empty) should ===(true)
|
state(g, dc1a1).convergence(Set.empty) should ===(true)
|
||||||
|
|
||||||
// but dc2 cannot
|
// but dc2 cannot
|
||||||
g.dcLeader("dc2", dc2c1.uniqueAddress) should ===(Some(dc2c1.uniqueAddress))
|
state(g, dc2c1).leader should ===(Some(dc2c1.uniqueAddress))
|
||||||
g.convergence("dc2", dc2c1.uniqueAddress, Set.empty) should ===(false)
|
state(g, dc2c1).convergence(Set.empty) should ===(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
"reach convergence per data center even if another data center contains unreachable" in {
|
"reach convergence per data center even if another data center contains unreachable" in {
|
||||||
|
|
@ -251,12 +251,12 @@ class GossipSpec extends WordSpec with Matchers {
|
||||||
.seen(dc2d1.uniqueAddress)
|
.seen(dc2d1.uniqueAddress)
|
||||||
|
|
||||||
// this data center doesn't care about dc2 having reachability problems and can reach convergence
|
// this data center doesn't care about dc2 having reachability problems and can reach convergence
|
||||||
g.dcLeader("dc1", dc1a1.uniqueAddress) should ===(Some(dc1a1.uniqueAddress))
|
state(g, dc1a1).leader should ===(Some(dc1a1.uniqueAddress))
|
||||||
g.convergence("dc1", dc1a1.uniqueAddress, Set.empty) should ===(true)
|
state(g, dc1a1).convergence(Set.empty) should ===(true)
|
||||||
|
|
||||||
// this data center is cannot reach convergence because of unreachability within the data center
|
// this data center is cannot reach convergence because of unreachability within the data center
|
||||||
g.dcLeader("dc2", dc2c1.uniqueAddress) should ===(Some(dc2c1.uniqueAddress))
|
state(g, dc2c1).leader should ===(Some(dc2c1.uniqueAddress))
|
||||||
g.convergence("dc2", dc2c1.uniqueAddress, Set.empty) should ===(false)
|
state(g, dc2c1).convergence(Set.empty) should ===(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
"reach convergence per data center even if there is unreachable nodes in another data center" in {
|
"reach convergence per data center even if there is unreachable nodes in another data center" in {
|
||||||
|
|
@ -271,11 +271,11 @@ class GossipSpec extends WordSpec with Matchers {
|
||||||
.seen(dc2d1.uniqueAddress)
|
.seen(dc2d1.uniqueAddress)
|
||||||
|
|
||||||
// neither data center is affected by the inter data center unreachability as far as convergence goes
|
// neither data center is affected by the inter data center unreachability as far as convergence goes
|
||||||
g.dcLeader("dc1", dc1a1.uniqueAddress) should ===(Some(dc1a1.uniqueAddress))
|
state(g, dc1a1).leader should ===(Some(dc1a1.uniqueAddress))
|
||||||
g.convergence("dc1", dc1a1.uniqueAddress, Set.empty) should ===(true)
|
state(g, dc1a1).convergence(Set.empty) should ===(true)
|
||||||
|
|
||||||
g.dcLeader("dc2", dc2c1.uniqueAddress) should ===(Some(dc2c1.uniqueAddress))
|
state(g, dc2c1).leader should ===(Some(dc2c1.uniqueAddress))
|
||||||
g.convergence("dc2", dc2c1.uniqueAddress, Set.empty) should ===(true)
|
state(g, dc2c1).convergence(Set.empty) should ===(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore cross data center unreachability when determining inside of data center reachability" in {
|
"ignore cross data center unreachability when determining inside of data center reachability" in {
|
||||||
|
|
@ -291,10 +291,10 @@ class GossipSpec extends WordSpec with Matchers {
|
||||||
g.isReachable(dc2c1.uniqueAddress, dc2d1.uniqueAddress) should ===(true)
|
g.isReachable(dc2c1.uniqueAddress, dc2d1.uniqueAddress) should ===(true)
|
||||||
g.isReachable(dc2d1.uniqueAddress, dc2c1.uniqueAddress) should ===(true)
|
g.isReachable(dc2d1.uniqueAddress, dc2c1.uniqueAddress) should ===(true)
|
||||||
|
|
||||||
g.isReachableExcludingDownedObservers(dc1a1.dataCenter, dc1b1.uniqueAddress) should ===(true)
|
state(g, dc1a1).isReachableExcludingDownedObservers(dc1b1.uniqueAddress) should ===(true)
|
||||||
g.isReachableExcludingDownedObservers(dc1b1.dataCenter, dc1a1.uniqueAddress) should ===(true)
|
state(g, dc1b1).isReachableExcludingDownedObservers(dc1a1.uniqueAddress) should ===(true)
|
||||||
g.isReachableExcludingDownedObservers(dc2c1.dataCenter, dc2d1.uniqueAddress) should ===(true)
|
state(g, dc2c1).isReachableExcludingDownedObservers(dc2d1.uniqueAddress) should ===(true)
|
||||||
g.isReachableExcludingDownedObservers(dc2d1.dataCenter, dc2c1.uniqueAddress) should ===(true)
|
state(g, dc2d1).isReachableExcludingDownedObservers(dc2c1.uniqueAddress) should ===(true)
|
||||||
|
|
||||||
// between data centers it matters though
|
// between data centers it matters though
|
||||||
g.isReachable(dc1a1.uniqueAddress, dc2c1.uniqueAddress) should ===(false)
|
g.isReachable(dc1a1.uniqueAddress, dc2c1.uniqueAddress) should ===(false)
|
||||||
|
|
@ -304,22 +304,22 @@ class GossipSpec extends WordSpec with Matchers {
|
||||||
g.isReachable(dc2d1.uniqueAddress, dc1a1.uniqueAddress) should ===(true)
|
g.isReachable(dc2d1.uniqueAddress, dc1a1.uniqueAddress) should ===(true)
|
||||||
|
|
||||||
// this one looks at all unreachable-entries for the to-address
|
// this one looks at all unreachable-entries for the to-address
|
||||||
g.isReachableExcludingDownedObservers(dc1a1.dataCenter, dc2c1.uniqueAddress) should ===(false)
|
state(g, dc1a1).isReachableExcludingDownedObservers(dc2c1.uniqueAddress) should ===(false)
|
||||||
g.isReachableExcludingDownedObservers(dc1b1.dataCenter, dc2c1.uniqueAddress) should ===(false)
|
state(g, dc1b1).isReachableExcludingDownedObservers(dc2c1.uniqueAddress) should ===(false)
|
||||||
g.isReachableExcludingDownedObservers(dc2c1.dataCenter, dc1a1.uniqueAddress) should ===(false)
|
state(g, dc2c1).isReachableExcludingDownedObservers(dc1a1.uniqueAddress) should ===(false)
|
||||||
g.isReachableExcludingDownedObservers(dc2d1.dataCenter, dc1a1.uniqueAddress) should ===(false)
|
state(g, dc2d1).isReachableExcludingDownedObservers(dc1a1.uniqueAddress) should ===(false)
|
||||||
|
|
||||||
// between the two other nodes there is no unreachability
|
// between the two other nodes there is no unreachability
|
||||||
g.isReachable(dc1b1.uniqueAddress, dc2d1.uniqueAddress) should ===(true)
|
g.isReachable(dc1b1.uniqueAddress, dc2d1.uniqueAddress) should ===(true)
|
||||||
g.isReachable(dc2d1.uniqueAddress, dc1b1.uniqueAddress) should ===(true)
|
g.isReachable(dc2d1.uniqueAddress, dc1b1.uniqueAddress) should ===(true)
|
||||||
|
|
||||||
g.isReachableExcludingDownedObservers(dc1b1.dataCenter, dc2d1.uniqueAddress) should ===(true)
|
state(g, dc1b1).isReachableExcludingDownedObservers(dc2d1.uniqueAddress) should ===(true)
|
||||||
g.isReachableExcludingDownedObservers(dc2d1.dataCenter, dc1b1.uniqueAddress) should ===(true)
|
state(g, dc2d1).isReachableExcludingDownedObservers(dc1b1.uniqueAddress) should ===(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
"not returning a downed data center leader" in {
|
"not returning a downed data center leader" in {
|
||||||
val g = Gossip(members = SortedSet(dc1a1.copy(Down), dc1b1))
|
val g = Gossip(members = SortedSet(dc1a1.copy(Down), dc1b1))
|
||||||
g.leaderOf("dc1", g.members, dc1b1.uniqueAddress) should ===(Some(dc1b1.uniqueAddress))
|
state(g, dc1b1).leaderOf(g.members) should ===(Some(dc1b1.uniqueAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
"ignore cross data center unreachability when determining data center leader" in {
|
"ignore cross data center unreachability when determining data center leader" in {
|
||||||
|
|
@ -329,15 +329,11 @@ class GossipSpec extends WordSpec with Matchers {
|
||||||
|
|
||||||
val g = Gossip(members = SortedSet(dc1a1, dc1b1, dc2c1, dc2d1), overview = GossipOverview(reachability = r1))
|
val g = Gossip(members = SortedSet(dc1a1, dc1b1, dc2c1, dc2d1), overview = GossipOverview(reachability = r1))
|
||||||
|
|
||||||
g.leaderOf("dc1", g.members, dc1a1.uniqueAddress) should ===(Some(dc1a1.uniqueAddress))
|
state(g, dc1a1).leaderOf(g.members) should ===(Some(dc1a1.uniqueAddress))
|
||||||
g.leaderOf("dc1", g.members, dc1b1.uniqueAddress) should ===(Some(dc1a1.uniqueAddress))
|
state(g, dc1b1).leaderOf(g.members) should ===(Some(dc1a1.uniqueAddress))
|
||||||
g.leaderOf("dc1", g.members, dc2c1.uniqueAddress) should ===(Some(dc1a1.uniqueAddress))
|
|
||||||
g.leaderOf("dc1", g.members, dc2d1.uniqueAddress) should ===(Some(dc1a1.uniqueAddress))
|
|
||||||
|
|
||||||
g.leaderOf("dc2", g.members, dc1a1.uniqueAddress) should ===(Some(dc2c1.uniqueAddress))
|
state(g, dc2c1).leaderOf(g.members) should ===(Some(dc2c1.uniqueAddress))
|
||||||
g.leaderOf("dc2", g.members, dc1b1.uniqueAddress) should ===(Some(dc2c1.uniqueAddress))
|
state(g, dc2d1).leaderOf(g.members) should ===(Some(dc2c1.uniqueAddress))
|
||||||
g.leaderOf("dc2", g.members, dc2c1.uniqueAddress) should ===(Some(dc2c1.uniqueAddress))
|
|
||||||
g.leaderOf("dc2", g.members, dc2d1.uniqueAddress) should ===(Some(dc2c1.uniqueAddress))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO test coverage for when leaderOf returns None - I have not been able to figure it out
|
// TODO test coverage for when leaderOf returns None - I have not been able to figure it out
|
||||||
|
|
|
||||||
|
|
@ -1221,14 +1221,6 @@ object MiMa extends AutoPlugin {
|
||||||
// #22881 Make sure connections are aborted correctly on Windows
|
// #22881 Make sure connections are aborted correctly on Windows
|
||||||
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.io.ChannelRegistration.cancel"),
|
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.io.ChannelRegistration.cancel"),
|
||||||
|
|
||||||
// #23231 multi-DC Sharding
|
|
||||||
ProblemFilters.exclude[IncompatibleResultTypeProblem]("akka.cluster.ddata.Replicator.leader"),
|
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.Replicator.receiveLeaderChanged"),
|
|
||||||
ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.cluster.ddata.Replicator.leader_="),
|
|
||||||
FilterAnyProblemStartingWith("akka.cluster.sharding.ClusterShardingGuardian"),
|
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.sharding.ShardRegion.proxyProps"),
|
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.sharding.ShardRegion.this"),
|
|
||||||
|
|
||||||
// #23144 recoverWithRetries cleanup
|
// #23144 recoverWithRetries cleanup
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.fusing.RecoverWith.InfiniteRetries"),
|
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.stream.impl.fusing.RecoverWith.InfiniteRetries"),
|
||||||
|
|
||||||
|
|
@ -1237,23 +1229,29 @@ object MiMa extends AutoPlugin {
|
||||||
|
|
||||||
// #23023 added a new overload with implementation to trait, so old transport implementations compiled against
|
// #23023 added a new overload with implementation to trait, so old transport implementations compiled against
|
||||||
// older versions will be missing the method. We accept that incompatibility for now.
|
// older versions will be missing the method. We accept that incompatibility for now.
|
||||||
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.transport.AssociationHandle.disassociate"),
|
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.remote.transport.AssociationHandle.disassociate")
|
||||||
|
),
|
||||||
|
"2.5.3" -> Seq(
|
||||||
|
// #23231 multi-DC Sharding
|
||||||
|
ProblemFilters.exclude[IncompatibleResultTypeProblem]("akka.cluster.ddata.Replicator.leader"),
|
||||||
|
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ddata.Replicator.receiveLeaderChanged"),
|
||||||
|
ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.cluster.ddata.Replicator.leader_="),
|
||||||
|
FilterAnyProblemStartingWith("akka.cluster.sharding.ClusterShardingGuardian"),
|
||||||
|
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.sharding.ShardRegion.proxyProps"),
|
||||||
|
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.sharding.ShardRegion.this"),
|
||||||
|
|
||||||
// #23228 single leader per cluster data center
|
// #23228 single leader per cluster data center
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.Gossip.apply"),
|
FilterAnyProblemStartingWith("akka.cluster.Gossip"),
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.Gossip.copy"),
|
FilterAnyProblemStartingWith("akka.cluster.ClusterCoreDaemon"),
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.Gossip.this"),
|
FilterAnyProblemStartingWith("akka.cluster.ClusterDomainEventPublisher"),
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.Gossip.convergence"),
|
FilterAnyProblemStartingWith("akka.cluster.InternalClusterAction"),
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.Gossip.isLeader"),
|
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterEvent.diffReachable"),
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.Gossip.leader"),
|
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.Gossip.leaderOf"),
|
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.Gossip.roleLeader"),
|
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterCoreDaemon.NumberOfGossipsBeforeShutdownWhenLeaderExits"),
|
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterCoreDaemon.vclockName"),
|
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterCoreDaemon.MaxGossipsBeforeShuttingDownMyself"),
|
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterEvent.diffLeader"),
|
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterEvent.diffLeader"),
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterEvent.diffRolesLeader"),
|
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterEvent.diffRolesLeader"),
|
||||||
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterEvent.diffSeen"),
|
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterEvent.diffSeen"),
|
||||||
|
ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.cluster.ClusterEvent.diffReachability"),
|
||||||
|
ProblemFilters.exclude[DirectMissingMethodProblem]("akka.cluster.ClusterEvent.diffUnreachable"),
|
||||||
|
ProblemFilters.exclude[IncompatibleMethTypeProblem]("akka.cluster.ClusterEvent.diffMemberEvents"),
|
||||||
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.protobuf.msg.ClusterMessages#GossipOrBuilder.getTombstonesCount"),
|
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.protobuf.msg.ClusterMessages#GossipOrBuilder.getTombstonesCount"),
|
||||||
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.protobuf.msg.ClusterMessages#GossipOrBuilder.getTombstones"),
|
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.protobuf.msg.ClusterMessages#GossipOrBuilder.getTombstones"),
|
||||||
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.protobuf.msg.ClusterMessages#GossipOrBuilder.getTombstonesList"),
|
ProblemFilters.exclude[ReversedMissingMethodProblem]("akka.cluster.protobuf.msg.ClusterMessages#GossipOrBuilder.getTombstonesList"),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue