* some cluster logging improvements * most logger names are actually good, when using ActorLogging since config can be setup on the package (prefix) * override logSource when StageLogging is used * replace system.log with more specific logger
682 lines
24 KiB
Scala
682 lines
24 KiB
Scala
/*
|
|
* Copyright (C) 2009-2019 Lightbend Inc. <https://www.lightbend.com>
|
|
*/
|
|
|
|
package akka.cluster
|
|
|
|
import language.postfixOps
|
|
import scala.collection.immutable
|
|
import scala.collection.immutable.{ SortedSet, VectorBuilder }
|
|
import akka.actor.{ Actor, ActorRef, Address }
|
|
import akka.cluster.ClusterSettings.DataCenter
|
|
import akka.cluster.ClusterEvent._
|
|
import akka.cluster.MemberStatus._
|
|
import akka.event.EventStream
|
|
import akka.dispatch.{ RequiresMessageQueue, UnboundedMessageQueueSemantics }
|
|
import akka.actor.DeadLetterSuppression
|
|
import akka.annotation.{ DoNotInherit, InternalApi }
|
|
import akka.util.ccompat._
|
|
|
|
import scala.runtime.AbstractFunction5
|
|
|
|
import com.github.ghik.silencer.silent
|
|
|
|
/**
|
|
* Domain events published to the event bus.
|
|
* Subscribe with:
|
|
* {{{
|
|
* Cluster(system).subscribe(actorRef, classOf[ClusterDomainEvent])
|
|
* }}}
|
|
*/
|
|
object ClusterEvent {
|
|
|
|
sealed abstract class SubscriptionInitialStateMode
|
|
|
|
/**
|
|
* When using this subscription mode a snapshot of
|
|
* [[akka.cluster.ClusterEvent.CurrentClusterState]] will be sent to the
|
|
* subscriber as the first message.
|
|
*/
|
|
case object InitialStateAsSnapshot extends SubscriptionInitialStateMode
|
|
|
|
/**
|
|
* When using this subscription mode the events corresponding
|
|
* to the current state will be sent to the subscriber to mimic what you would
|
|
* have seen if you were listening to the events when they occurred in the past.
|
|
*/
|
|
case object InitialStateAsEvents extends SubscriptionInitialStateMode
|
|
|
|
/**
|
|
* Java API
|
|
*/
|
|
def initialStateAsSnapshot = InitialStateAsSnapshot
|
|
|
|
/**
|
|
* Java API
|
|
*/
|
|
def initialStateAsEvents = InitialStateAsEvents
|
|
|
|
/**
|
|
* Marker interface for cluster domain events.
|
|
*
|
|
* Not intended for user extension.
|
|
*/
|
|
@DoNotInherit
|
|
trait ClusterDomainEvent extends DeadLetterSuppression
|
|
|
|
// for binary compatibility (used to be a case class)
|
|
object CurrentClusterState
|
|
extends AbstractFunction5[
|
|
immutable.SortedSet[Member],
|
|
Set[Member],
|
|
Set[Address],
|
|
Option[Address],
|
|
Map[String, Option[Address]],
|
|
CurrentClusterState] {
|
|
|
|
def apply(
|
|
members: immutable.SortedSet[Member] = immutable.SortedSet.empty,
|
|
unreachable: Set[Member] = Set.empty,
|
|
seenBy: Set[Address] = Set.empty,
|
|
leader: Option[Address] = None,
|
|
roleLeaderMap: Map[String, Option[Address]] = Map.empty): CurrentClusterState =
|
|
new CurrentClusterState(members, unreachable, seenBy, leader, roleLeaderMap)
|
|
|
|
def unapply(cs: CurrentClusterState): Option[
|
|
(immutable.SortedSet[Member], Set[Member], Set[Address], Option[Address], Map[String, Option[Address]])] =
|
|
Some((cs.members, cs.unreachable, cs.seenBy, cs.leader, cs.roleLeaderMap))
|
|
|
|
}
|
|
|
|
/**
|
|
* Current snapshot state of the cluster. Sent to new subscriber.
|
|
*
|
|
* @param leader leader of the data center of this node
|
|
*/
|
|
@SerialVersionUID(2)
|
|
final class CurrentClusterState(
|
|
val members: immutable.SortedSet[Member],
|
|
val unreachable: Set[Member],
|
|
val seenBy: Set[Address],
|
|
val leader: Option[Address],
|
|
val roleLeaderMap: Map[String, Option[Address]],
|
|
val unreachableDataCenters: Set[DataCenter])
|
|
extends Product5[
|
|
immutable.SortedSet[Member],
|
|
Set[Member],
|
|
Set[Address],
|
|
Option[Address],
|
|
Map[String, Option[Address]]]
|
|
with Serializable {
|
|
|
|
// for binary compatibility
|
|
def this(
|
|
members: immutable.SortedSet[Member] = immutable.SortedSet.empty,
|
|
unreachable: Set[Member] = Set.empty,
|
|
seenBy: Set[Address] = Set.empty,
|
|
leader: Option[Address] = None,
|
|
roleLeaderMap: Map[String, Option[Address]] = Map.empty) =
|
|
this(members, unreachable, seenBy, leader, roleLeaderMap, Set.empty)
|
|
|
|
/**
|
|
* Java API: get current member list.
|
|
*/
|
|
def getMembers: java.lang.Iterable[Member] = {
|
|
import akka.util.ccompat.JavaConverters._
|
|
members.asJava
|
|
}
|
|
|
|
/**
|
|
* Java API: get current unreachable set.
|
|
*/
|
|
@silent("deprecated")
|
|
def getUnreachable: java.util.Set[Member] =
|
|
scala.collection.JavaConverters.setAsJavaSetConverter(unreachable).asJava
|
|
|
|
/**
|
|
* Java API: All data centers in the cluster
|
|
*/
|
|
@silent("deprecated")
|
|
def getUnreachableDataCenters: java.util.Set[String] =
|
|
scala.collection.JavaConverters.setAsJavaSetConverter(unreachableDataCenters).asJava
|
|
|
|
/**
|
|
* Java API: get current “seen-by” set.
|
|
*/
|
|
@silent("deprecated")
|
|
def getSeenBy: java.util.Set[Address] =
|
|
scala.collection.JavaConverters.setAsJavaSetConverter(seenBy).asJava
|
|
|
|
/**
|
|
* Java API: get address of current data center leader, or null if none
|
|
*/
|
|
def getLeader: Address = leader orNull
|
|
|
|
/**
|
|
* get address of current leader, if any, within the data center that has the given role
|
|
*/
|
|
def roleLeader(role: String): Option[Address] = roleLeaderMap.getOrElse(role, None)
|
|
|
|
/**
|
|
* Java API: get address of current leader, if any, within the data center that has the given role
|
|
* or null if no such node exists
|
|
*/
|
|
def getRoleLeader(role: String): Address = roleLeaderMap.get(role).flatten.orNull
|
|
|
|
/**
|
|
* All node roles in the cluster
|
|
*/
|
|
def allRoles: Set[String] = roleLeaderMap.keySet
|
|
|
|
/**
|
|
* Java API: All node roles in the cluster
|
|
*/
|
|
@silent("deprecated")
|
|
def getAllRoles: java.util.Set[String] =
|
|
scala.collection.JavaConverters.setAsJavaSetConverter(allRoles).asJava
|
|
|
|
/**
|
|
* All data centers in the cluster
|
|
*/
|
|
def allDataCenters: Set[String] = members.iterator.map(_.dataCenter).to(immutable.Set)
|
|
|
|
/**
|
|
* Java API: All data centers in the cluster
|
|
*/
|
|
@silent("deprecated")
|
|
def getAllDataCenters: java.util.Set[String] =
|
|
scala.collection.JavaConverters.setAsJavaSetConverter(allDataCenters).asJava
|
|
|
|
/**
|
|
* Replace the set of unreachable datacenters with the given set
|
|
*/
|
|
def withUnreachableDataCenters(unreachableDataCenters: Set[DataCenter]): CurrentClusterState =
|
|
new CurrentClusterState(members, unreachable, seenBy, leader, roleLeaderMap, unreachableDataCenters)
|
|
|
|
/**
|
|
* INTERNAL API
|
|
* Returns true if the address is a cluster member and that member is `MemberStatus.Up`.
|
|
*/
|
|
@InternalApi
|
|
private[akka] def isMemberUp(address: Address): Boolean =
|
|
members.exists(m => m.address == address && m.status == MemberStatus.Up)
|
|
|
|
// for binary compatibility (used to be a case class)
|
|
def copy(
|
|
members: immutable.SortedSet[Member] = this.members,
|
|
unreachable: Set[Member] = this.unreachable,
|
|
seenBy: Set[Address] = this.seenBy,
|
|
leader: Option[Address] = this.leader,
|
|
roleLeaderMap: Map[String, Option[Address]] = this.roleLeaderMap) =
|
|
new CurrentClusterState(members, unreachable, seenBy, leader, roleLeaderMap, unreachableDataCenters)
|
|
|
|
override def equals(other: Any): Boolean = other match {
|
|
case that: CurrentClusterState =>
|
|
(this eq that) || (members == that.members &&
|
|
unreachable == that.unreachable &&
|
|
seenBy == that.seenBy &&
|
|
leader == that.leader &&
|
|
roleLeaderMap == that.roleLeaderMap)
|
|
case _ => false
|
|
}
|
|
|
|
override def hashCode(): Int = {
|
|
val state = Seq(members, unreachable, seenBy, leader, roleLeaderMap)
|
|
state.map(_.hashCode()).foldLeft(0)((a, b) => 31 * a + b)
|
|
}
|
|
|
|
// Product5
|
|
override def productPrefix = "CurrentClusterState"
|
|
def _1: SortedSet[Member] = members
|
|
def _2: Set[Member] = unreachable
|
|
def _3: Set[Address] = seenBy
|
|
def _4: Option[Address] = leader
|
|
def _5: Map[String, Option[Address]] = roleLeaderMap
|
|
def canEqual(that: Any): Boolean = that.isInstanceOf[CurrentClusterState]
|
|
|
|
override def toString = s"CurrentClusterState($members, $unreachable, $seenBy, $leader, $roleLeaderMap)"
|
|
}
|
|
|
|
/**
|
|
* Marker interface for membership events.
|
|
* Published when the state change is first seen on a node.
|
|
* The state change was performed by the leader when there was
|
|
* convergence on the leader node, i.e. all members had seen previous
|
|
* state.
|
|
*/
|
|
sealed trait MemberEvent extends ClusterDomainEvent {
|
|
def member: Member
|
|
}
|
|
|
|
/**
|
|
* Member status changed to Joining.
|
|
*/
|
|
final case class MemberJoined(member: Member) extends MemberEvent {
|
|
if (member.status != Joining) throw new IllegalArgumentException("Expected Joining status, got: " + member)
|
|
}
|
|
|
|
/**
|
|
* Member status changed to WeaklyUp.
|
|
* A joining member can be moved to `WeaklyUp` if convergence
|
|
* cannot be reached, i.e. there are unreachable nodes.
|
|
* It will be moved to `Up` when convergence is reached.
|
|
*/
|
|
final case class MemberWeaklyUp(member: Member) extends MemberEvent {
|
|
if (member.status != WeaklyUp) throw new IllegalArgumentException("Expected WeaklyUp status, got: " + member)
|
|
}
|
|
|
|
/**
|
|
* Member status changed to Up.
|
|
*/
|
|
final case class MemberUp(member: Member) extends MemberEvent {
|
|
if (member.status != Up) throw new IllegalArgumentException("Expected Up status, got: " + member)
|
|
}
|
|
|
|
/**
|
|
* Member status changed to Leaving.
|
|
*/
|
|
final case class MemberLeft(member: Member) extends MemberEvent {
|
|
if (member.status != Leaving) throw new IllegalArgumentException("Expected Leaving status, got: " + member)
|
|
}
|
|
|
|
/**
|
|
* Member status changed to `MemberStatus.Exiting` and will be removed
|
|
* when all members have seen the `Exiting` status.
|
|
*/
|
|
final case class MemberExited(member: Member) extends MemberEvent {
|
|
if (member.status != Exiting) throw new IllegalArgumentException("Expected Exiting status, got: " + member)
|
|
}
|
|
|
|
/**
|
|
* Member status changed to `MemberStatus.Down` and will be removed
|
|
* when all members have seen the `Down` status.
|
|
*/
|
|
final case class MemberDowned(member: Member) extends MemberEvent {
|
|
if (member.status != Down) throw new IllegalArgumentException("Expected Down status, got: " + member)
|
|
}
|
|
|
|
/**
|
|
* Member completely removed from the cluster.
|
|
* When `previousStatus` is `MemberStatus.Down` the node was removed
|
|
* after being detected as unreachable and downed.
|
|
* When `previousStatus` is `MemberStatus.Exiting` the node was removed
|
|
* after graceful leaving and exiting.
|
|
*/
|
|
final case class MemberRemoved(member: Member, previousStatus: MemberStatus) extends MemberEvent {
|
|
if (member.status != Removed) throw new IllegalArgumentException("Expected Removed status, got: " + member)
|
|
}
|
|
|
|
/**
|
|
* Leader of the cluster data center of this node changed. Published when the state change
|
|
* is first seen on a node.
|
|
*/
|
|
final case class LeaderChanged(leader: Option[Address]) extends ClusterDomainEvent {
|
|
|
|
/**
|
|
* Java API
|
|
* @return address of current leader, or null if none
|
|
*/
|
|
def getLeader: Address = leader orNull
|
|
}
|
|
|
|
/**
|
|
* First member (leader) of the members within a role set (in the same data center as this node,
|
|
* if data centers are used) changed.
|
|
* Published when the state change is first seen on a node.
|
|
*/
|
|
final case class RoleLeaderChanged(role: String, leader: Option[Address]) extends ClusterDomainEvent {
|
|
|
|
/**
|
|
* Java API
|
|
* @return address of current leader, or null if none
|
|
*/
|
|
def getLeader: Address = leader orNull
|
|
}
|
|
|
|
/**
|
|
* This event is published when the cluster node is shutting down,
|
|
* before the final [[MemberRemoved]] events are published.
|
|
*/
|
|
final case object ClusterShuttingDown extends ClusterDomainEvent
|
|
|
|
/**
|
|
* Java API: get the singleton instance of `ClusterShuttingDown` event
|
|
*/
|
|
def getClusterShuttingDownInstance = ClusterShuttingDown
|
|
|
|
/**
|
|
* Marker interface to facilitate subscription of
|
|
* both [[UnreachableMember]] and [[ReachableMember]].
|
|
*/
|
|
sealed trait ReachabilityEvent extends ClusterDomainEvent {
|
|
def member: Member
|
|
}
|
|
|
|
/**
|
|
* A member is considered as unreachable by the failure detector.
|
|
*/
|
|
final case class UnreachableMember(member: Member) extends ReachabilityEvent
|
|
|
|
/**
|
|
* A member is considered as reachable by the failure detector
|
|
* after having been unreachable.
|
|
* @see [[UnreachableMember]]
|
|
*/
|
|
final case class ReachableMember(member: Member) extends ReachabilityEvent
|
|
|
|
/**
|
|
* Marker interface to facilitate subscription of
|
|
* both [[UnreachableDataCenter]] and [[ReachableDataCenter]].
|
|
*/
|
|
sealed trait DataCenterReachabilityEvent extends ClusterDomainEvent
|
|
|
|
/**
|
|
* A data center is considered as unreachable when any members from the data center are unreachable
|
|
*/
|
|
final case class UnreachableDataCenter(dataCenter: DataCenter) extends DataCenterReachabilityEvent
|
|
|
|
/**
|
|
* A data center is considered reachable when all members from the data center are reachable
|
|
*/
|
|
final case class ReachableDataCenter(dataCenter: DataCenter) extends DataCenterReachabilityEvent
|
|
|
|
/**
|
|
* INTERNAL API
|
|
* The nodes that have seen current version of the Gossip.
|
|
*/
|
|
@ccompatUsedUntil213
|
|
private[cluster] final case class SeenChanged(convergence: Boolean, seenBy: Set[Address]) extends ClusterDomainEvent
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[cluster] final case class ReachabilityChanged(reachability: Reachability) extends ClusterDomainEvent
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[cluster] final case class CurrentInternalStats(gossipStats: GossipStats, vclockStats: VectorClockStats)
|
|
extends ClusterDomainEvent
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[cluster] def diffUnreachable(
|
|
oldState: MembershipState,
|
|
newState: MembershipState): immutable.Seq[UnreachableMember] =
|
|
if (newState eq oldState) Nil
|
|
else {
|
|
val newGossip = newState.latestGossip
|
|
val oldUnreachableNodes = oldState.dcReachabilityNoOutsideNodes.allUnreachableOrTerminated
|
|
newState.dcReachabilityNoOutsideNodes.allUnreachableOrTerminated.iterator
|
|
.collect {
|
|
case node if !oldUnreachableNodes.contains(node) && node != newState.selfUniqueAddress =>
|
|
UnreachableMember(newGossip.member(node))
|
|
}
|
|
.to(immutable.IndexedSeq)
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[cluster] def diffReachable(
|
|
oldState: MembershipState,
|
|
newState: MembershipState): immutable.Seq[ReachableMember] =
|
|
if (newState eq oldState) Nil
|
|
else {
|
|
val newGossip = newState.latestGossip
|
|
oldState.dcReachabilityNoOutsideNodes.allUnreachable.iterator
|
|
.collect {
|
|
case node
|
|
if newGossip.hasMember(node) && newState.dcReachabilityNoOutsideNodes.isReachable(node) && node != newState.selfUniqueAddress =>
|
|
ReachableMember(newGossip.member(node))
|
|
}
|
|
.to(immutable.IndexedSeq)
|
|
}
|
|
|
|
/**
|
|
* Internal API
|
|
*/
|
|
private[cluster] def isDataCenterReachable(state: MembershipState)(otherDc: DataCenter): Boolean = {
|
|
val unrelatedDcNodes = state.latestGossip.members.collect {
|
|
case m if m.dataCenter != otherDc && m.dataCenter != state.selfDc => m.uniqueAddress
|
|
}
|
|
|
|
val reachabilityForOtherDc = state.dcReachabilityWithoutObservationsWithin.remove(unrelatedDcNodes)
|
|
reachabilityForOtherDc.allUnreachable.isEmpty
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[cluster] def diffUnreachableDataCenter(
|
|
oldState: MembershipState,
|
|
newState: MembershipState): immutable.Seq[UnreachableDataCenter] = {
|
|
if (newState eq oldState) Nil
|
|
else {
|
|
val otherDcs = (oldState.latestGossip.allDataCenters
|
|
.union(newState.latestGossip.allDataCenters)) - newState.selfDc
|
|
|
|
val oldUnreachableDcs = otherDcs.filterNot(isDataCenterReachable(oldState))
|
|
val currentUnreachableDcs = otherDcs.filterNot(isDataCenterReachable(newState))
|
|
|
|
currentUnreachableDcs.diff(oldUnreachableDcs).iterator.map(UnreachableDataCenter).to(immutable.IndexedSeq)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
private[cluster] def diffReachableDataCenter(
|
|
oldState: MembershipState,
|
|
newState: MembershipState): immutable.Seq[ReachableDataCenter] = {
|
|
if (newState eq oldState) Nil
|
|
else {
|
|
val otherDcs = (oldState.latestGossip.allDataCenters
|
|
.union(newState.latestGossip.allDataCenters)) - newState.selfDc
|
|
|
|
val oldUnreachableDcs = otherDcs.filterNot(isDataCenterReachable(oldState))
|
|
val currentUnreachableDcs = otherDcs.filterNot(isDataCenterReachable(newState))
|
|
|
|
oldUnreachableDcs.diff(currentUnreachableDcs).iterator.map(ReachableDataCenter).to(immutable.IndexedSeq)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API.
|
|
*/
|
|
private[cluster] def diffMemberEvents(
|
|
oldState: MembershipState,
|
|
newState: MembershipState): immutable.Seq[MemberEvent] =
|
|
if (newState eq oldState) Nil
|
|
else {
|
|
val oldGossip = oldState.latestGossip
|
|
val newGossip = newState.latestGossip
|
|
val newMembers = newGossip.members.diff(oldGossip.members)
|
|
val membersGroupedByAddress = List(newGossip.members, oldGossip.members).flatten.groupBy(_.uniqueAddress)
|
|
val changedMembers = membersGroupedByAddress.collect {
|
|
case (_, newMember :: oldMember :: Nil)
|
|
if newMember.status != oldMember.status || newMember.upNumber != oldMember.upNumber =>
|
|
newMember
|
|
}
|
|
val memberEvents = (newMembers ++ changedMembers).unsorted.collect {
|
|
case m if m.status == Joining => MemberJoined(m)
|
|
case m if m.status == WeaklyUp => MemberWeaklyUp(m)
|
|
case m if m.status == Up => MemberUp(m)
|
|
case m if m.status == Leaving => MemberLeft(m)
|
|
case m if m.status == Exiting => MemberExited(m)
|
|
case m if m.status == Down => MemberDowned(m)
|
|
// no events for other transitions
|
|
}
|
|
|
|
val removedMembers = oldGossip.members.diff(newGossip.members)
|
|
val removedEvents = removedMembers.unsorted.map(m => MemberRemoved(m.copy(status = Removed), m.status))
|
|
|
|
(new VectorBuilder[MemberEvent]() ++= removedEvents ++= memberEvents).result()
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
@InternalApi
|
|
private[cluster] def diffLeader(
|
|
oldState: MembershipState,
|
|
newState: MembershipState): immutable.Seq[LeaderChanged] = {
|
|
val newLeader = newState.leader
|
|
if (newLeader != oldState.leader) List(LeaderChanged(newLeader.map(_.address)))
|
|
else Nil
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
@InternalApi
|
|
private[cluster] def diffRolesLeader(oldState: MembershipState, newState: MembershipState): Set[RoleLeaderChanged] = {
|
|
for {
|
|
role <- oldState.latestGossip.allRoles.union(newState.latestGossip.allRoles)
|
|
newLeader = newState.roleLeader(role)
|
|
if newLeader != oldState.roleLeader(role)
|
|
} yield RoleLeaderChanged(role, newLeader.map(_.address))
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
@InternalApi
|
|
private[cluster] def diffSeen(oldState: MembershipState, newState: MembershipState): immutable.Seq[SeenChanged] =
|
|
if (oldState eq newState) Nil
|
|
else {
|
|
val newConvergence = newState.convergence(Set.empty)
|
|
val newSeenBy = newState.latestGossip.seenBy
|
|
if (newConvergence != oldState.convergence(Set.empty) || newSeenBy != oldState.latestGossip.seenBy)
|
|
List(SeenChanged(newConvergence, newSeenBy.map(_.address)))
|
|
else Nil
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API
|
|
*/
|
|
@InternalApi
|
|
private[cluster] def diffReachability(
|
|
oldState: MembershipState,
|
|
newState: MembershipState): immutable.Seq[ReachabilityChanged] =
|
|
if (newState.overview.reachability eq oldState.overview.reachability) Nil
|
|
else List(ReachabilityChanged(newState.overview.reachability))
|
|
|
|
}
|
|
|
|
/**
|
|
* INTERNAL API.
|
|
* Responsible for domain event subscriptions and publishing of
|
|
* domain events to event bus.
|
|
*/
|
|
private[cluster] final class ClusterDomainEventPublisher
|
|
extends Actor
|
|
with RequiresMessageQueue[UnboundedMessageQueueSemantics] {
|
|
import InternalClusterAction._
|
|
|
|
val cluster = Cluster(context.system)
|
|
val selfUniqueAddress = cluster.selfUniqueAddress
|
|
val emptyMembershipState = MembershipState(
|
|
Gossip.empty,
|
|
cluster.selfUniqueAddress,
|
|
cluster.settings.SelfDataCenter,
|
|
cluster.settings.MultiDataCenter.CrossDcConnections)
|
|
var membershipState: MembershipState = emptyMembershipState
|
|
def selfDc = cluster.settings.SelfDataCenter
|
|
|
|
override def preRestart(reason: Throwable, message: Option[Any]): Unit = {
|
|
// don't postStop when restarted, no children to stop
|
|
}
|
|
|
|
override def postStop(): Unit = {
|
|
// publish the final removed state before shutting down
|
|
publish(ClusterShuttingDown)
|
|
publishChanges(emptyMembershipState)
|
|
}
|
|
|
|
def receive = {
|
|
case PublishChanges(newState) => publishChanges(newState)
|
|
case currentStats: CurrentInternalStats => publishInternalStats(currentStats)
|
|
case SendCurrentClusterState(receiver) => sendCurrentClusterState(receiver)
|
|
case Subscribe(subscriber, initMode, to) => subscribe(subscriber, initMode, to)
|
|
case Unsubscribe(subscriber, to) => unsubscribe(subscriber, to)
|
|
case PublishEvent(event) => publish(event)
|
|
}
|
|
|
|
def eventStream: EventStream = context.system.eventStream
|
|
|
|
/**
|
|
* The current snapshot state corresponding to latest gossip
|
|
* to mimic what you would have seen if you were listening to the events.
|
|
*/
|
|
def sendCurrentClusterState(receiver: ActorRef): Unit = {
|
|
val unreachable: Set[Member] =
|
|
membershipState.dcReachabilityNoOutsideNodes.allUnreachableOrTerminated.collect {
|
|
case node if node != selfUniqueAddress => membershipState.latestGossip.member(node)
|
|
}
|
|
|
|
val unreachableDataCenters: Set[DataCenter] =
|
|
if (!membershipState.latestGossip.isMultiDc) Set.empty
|
|
else membershipState.latestGossip.allDataCenters.filterNot(isDataCenterReachable(membershipState))
|
|
|
|
val state = new CurrentClusterState(
|
|
members = membershipState.latestGossip.members,
|
|
unreachable = unreachable,
|
|
seenBy = membershipState.latestGossip.seenBy.map(_.address),
|
|
leader = membershipState.leader.map(_.address),
|
|
roleLeaderMap = membershipState.latestGossip.allRoles.iterator
|
|
.map(r => r -> membershipState.roleLeader(r).map(_.address))
|
|
.toMap,
|
|
unreachableDataCenters)
|
|
receiver ! state
|
|
}
|
|
|
|
def subscribe(subscriber: ActorRef, initMode: SubscriptionInitialStateMode, to: Set[Class[_]]): Unit = {
|
|
initMode match {
|
|
case InitialStateAsEvents =>
|
|
def pub(event: AnyRef): Unit = {
|
|
if (to.exists(_.isAssignableFrom(event.getClass)))
|
|
subscriber ! event
|
|
}
|
|
publishDiff(emptyMembershipState, membershipState, pub)
|
|
case InitialStateAsSnapshot =>
|
|
sendCurrentClusterState(subscriber)
|
|
}
|
|
|
|
to.foreach { eventStream.subscribe(subscriber, _) }
|
|
}
|
|
|
|
def unsubscribe(subscriber: ActorRef, to: Option[Class[_]]): Unit = to match {
|
|
case None => eventStream.unsubscribe(subscriber)
|
|
case Some(c) => eventStream.unsubscribe(subscriber, c)
|
|
}
|
|
|
|
def publishChanges(newState: MembershipState): Unit = {
|
|
val oldState = membershipState
|
|
// keep the latest state to be sent to new subscribers
|
|
membershipState = newState
|
|
publishDiff(oldState, newState, publish)
|
|
}
|
|
|
|
def publishDiff(oldState: MembershipState, newState: MembershipState, pub: AnyRef => Unit): Unit = {
|
|
diffMemberEvents(oldState, newState).foreach(pub)
|
|
diffUnreachable(oldState, newState).foreach(pub)
|
|
diffReachable(oldState, newState).foreach(pub)
|
|
diffUnreachableDataCenter(oldState, newState).foreach(pub)
|
|
diffReachableDataCenter(oldState, newState).foreach(pub)
|
|
diffLeader(oldState, newState).foreach(pub)
|
|
diffRolesLeader(oldState, newState).foreach(pub)
|
|
// publish internal SeenState for testing purposes
|
|
diffSeen(oldState, newState).foreach(pub)
|
|
diffReachability(oldState, newState).foreach(pub)
|
|
}
|
|
|
|
def publishInternalStats(currentStats: CurrentInternalStats): Unit = publish(currentStats)
|
|
|
|
def publish(event: AnyRef): Unit = eventStream.publish(event)
|
|
|
|
def clearState(): Unit = {
|
|
membershipState = emptyMembershipState
|
|
}
|
|
}
|