2013-08-27 15:14:53 +02:00
|
|
|
/**
|
2014-02-02 19:05:45 -06:00
|
|
|
* Copyright (C) 2009-2014 Typesafe Inc. <http://www.typesafe.com>
|
2013-08-27 15:14:53 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
package akka.cluster
|
|
|
|
|
|
|
|
|
|
import org.scalatest.WordSpec
|
2013-12-17 14:25:56 +01:00
|
|
|
import org.scalatest.Matchers
|
2013-08-27 15:14:53 +02:00
|
|
|
import akka.actor.Address
|
|
|
|
|
|
|
|
|
|
@org.junit.runner.RunWith(classOf[org.scalatest.junit.JUnitRunner])
|
2013-12-17 14:25:56 +01:00
|
|
|
class ReachabilitySpec extends WordSpec with Matchers {
|
2013-08-27 15:14:53 +02:00
|
|
|
|
|
|
|
|
import Reachability.{ Reachable, Unreachable, Terminated, Record }
|
|
|
|
|
|
|
|
|
|
val nodeA = UniqueAddress(Address("akka.tcp", "sys", "a", 2552), 1)
|
|
|
|
|
val nodeB = UniqueAddress(Address("akka.tcp", "sys", "b", 2552), 2)
|
|
|
|
|
val nodeC = UniqueAddress(Address("akka.tcp", "sys", "c", 2552), 3)
|
|
|
|
|
val nodeD = UniqueAddress(Address("akka.tcp", "sys", "d", 2552), 4)
|
|
|
|
|
val nodeE = UniqueAddress(Address("akka.tcp", "sys", "e", 2552), 5)
|
|
|
|
|
|
|
|
|
|
"Reachability table" must {
|
|
|
|
|
|
|
|
|
|
"be reachable when empty" in {
|
|
|
|
|
val r = Reachability.empty
|
2013-12-17 14:25:56 +01:00
|
|
|
r.isReachable(nodeA) should be(true)
|
|
|
|
|
r.allUnreachable should be(Set.empty)
|
2013-08-27 15:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"be unreachable when one observed unreachable" in {
|
|
|
|
|
val r = Reachability.empty.unreachable(nodeB, nodeA)
|
2013-12-17 14:25:56 +01:00
|
|
|
r.isReachable(nodeA) should be(false)
|
|
|
|
|
r.allUnreachable should be(Set(nodeA))
|
2013-08-27 15:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"not be reachable when terminated" in {
|
|
|
|
|
val r = Reachability.empty.terminated(nodeB, nodeA)
|
2013-12-17 14:25:56 +01:00
|
|
|
r.isReachable(nodeA) should be(false)
|
2013-08-27 15:14:53 +02:00
|
|
|
// allUnreachable doesn't include terminated
|
2013-12-17 14:25:56 +01:00
|
|
|
r.allUnreachable should be(Set.empty)
|
|
|
|
|
r.allUnreachableOrTerminated should be(Set(nodeA))
|
2013-08-27 15:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"not change terminated entry" in {
|
|
|
|
|
val r = Reachability.empty.terminated(nodeB, nodeA)
|
2013-12-17 14:25:56 +01:00
|
|
|
r.reachable(nodeB, nodeA) should be theSameInstanceAs (r)
|
|
|
|
|
r.unreachable(nodeB, nodeA) should be theSameInstanceAs (r)
|
2013-08-27 15:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"not change when same status" in {
|
|
|
|
|
val r = Reachability.empty.unreachable(nodeB, nodeA)
|
2013-12-17 14:25:56 +01:00
|
|
|
r.unreachable(nodeB, nodeA) should be theSameInstanceAs (r)
|
2013-08-27 15:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"be unreachable when some observed unreachable and others reachable" in {
|
|
|
|
|
val r = Reachability.empty.unreachable(nodeB, nodeA).unreachable(nodeC, nodeA).reachable(nodeD, nodeA)
|
2013-12-17 14:25:56 +01:00
|
|
|
r.isReachable(nodeA) should be(false)
|
2013-08-27 15:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"be reachable when all observed reachable again" in {
|
|
|
|
|
val r = Reachability.empty.unreachable(nodeB, nodeA).unreachable(nodeC, nodeA).
|
|
|
|
|
reachable(nodeB, nodeA).reachable(nodeC, nodeA).
|
|
|
|
|
unreachable(nodeB, nodeC).unreachable(nodeC, nodeB)
|
2013-12-17 14:25:56 +01:00
|
|
|
r.isReachable(nodeA) should be(true)
|
2013-08-27 15:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
2015-01-30 14:30:16 +01:00
|
|
|
"exclude observations from specific (downed) nodes" in {
|
|
|
|
|
val r = Reachability.empty.
|
|
|
|
|
unreachable(nodeC, nodeA).reachable(nodeC, nodeA).
|
|
|
|
|
unreachable(nodeC, nodeB).
|
|
|
|
|
unreachable(nodeB, nodeA).unreachable(nodeB, nodeC)
|
|
|
|
|
|
|
|
|
|
r.isReachable(nodeA) should be(false)
|
|
|
|
|
r.isReachable(nodeB) should be(false)
|
|
|
|
|
r.isReachable(nodeC) should be(false)
|
|
|
|
|
r.allUnreachableOrTerminated should be(Set(nodeA, nodeB, nodeC))
|
|
|
|
|
r.removeObservers(Set(nodeB)).allUnreachableOrTerminated should be(Set(nodeB))
|
|
|
|
|
}
|
|
|
|
|
|
2013-08-27 15:14:53 +02:00
|
|
|
"be pruned when all records of an observer are Reachable" in {
|
|
|
|
|
val r = Reachability.empty.
|
|
|
|
|
unreachable(nodeB, nodeA).unreachable(nodeB, nodeC).
|
|
|
|
|
unreachable(nodeD, nodeC).
|
|
|
|
|
reachable(nodeB, nodeA).reachable(nodeB, nodeC)
|
2013-12-17 14:25:56 +01:00
|
|
|
r.isReachable(nodeA) should be(true)
|
|
|
|
|
r.isReachable(nodeC) should be(false)
|
|
|
|
|
r.records should be(Vector(Record(nodeD, nodeC, Unreachable, 1L)))
|
2013-08-27 15:14:53 +02:00
|
|
|
|
|
|
|
|
val r2 = r.unreachable(nodeB, nodeD).unreachable(nodeB, nodeE)
|
2013-12-17 14:25:56 +01:00
|
|
|
r2.records.toSet should be(Set(
|
2013-08-27 15:14:53 +02:00
|
|
|
Record(nodeD, nodeC, Unreachable, 1L),
|
|
|
|
|
Record(nodeB, nodeD, Unreachable, 5L),
|
|
|
|
|
Record(nodeB, nodeE, Unreachable, 6L)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"have correct aggregated status" in {
|
|
|
|
|
val records = Vector(
|
|
|
|
|
Reachability.Record(nodeA, nodeB, Reachable, 2),
|
|
|
|
|
Reachability.Record(nodeC, nodeB, Unreachable, 2),
|
|
|
|
|
Reachability.Record(nodeA, nodeD, Unreachable, 3),
|
|
|
|
|
Reachability.Record(nodeD, nodeB, Terminated, 4))
|
|
|
|
|
val versions = Map(nodeA -> 3L, nodeC -> 3L, nodeD -> 4L)
|
|
|
|
|
val r = Reachability(records, versions)
|
2013-12-17 14:25:56 +01:00
|
|
|
r.status(nodeA) should be(Reachable)
|
|
|
|
|
r.status(nodeB) should be(Terminated)
|
|
|
|
|
r.status(nodeD) should be(Unreachable)
|
2013-08-27 15:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"have correct status for a mix of nodes" in {
|
|
|
|
|
val r = Reachability.empty.
|
|
|
|
|
unreachable(nodeB, nodeA).unreachable(nodeC, nodeA).unreachable(nodeD, nodeA).
|
|
|
|
|
unreachable(nodeC, nodeB).reachable(nodeC, nodeB).unreachable(nodeD, nodeB).
|
|
|
|
|
unreachable(nodeD, nodeC).reachable(nodeD, nodeC).
|
|
|
|
|
reachable(nodeE, nodeD).
|
|
|
|
|
unreachable(nodeA, nodeE).terminated(nodeB, nodeE)
|
|
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
r.status(nodeB, nodeA) should be(Unreachable)
|
|
|
|
|
r.status(nodeC, nodeA) should be(Unreachable)
|
|
|
|
|
r.status(nodeD, nodeA) should be(Unreachable)
|
2013-08-27 15:14:53 +02:00
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
r.status(nodeC, nodeB) should be(Reachable)
|
|
|
|
|
r.status(nodeD, nodeB) should be(Unreachable)
|
2013-08-27 15:14:53 +02:00
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
r.status(nodeA, nodeE) should be(Unreachable)
|
|
|
|
|
r.status(nodeB, nodeE) should be(Terminated)
|
2013-08-27 15:14:53 +02:00
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
r.isReachable(nodeA) should be(false)
|
|
|
|
|
r.isReachable(nodeB) should be(false)
|
|
|
|
|
r.isReachable(nodeC) should be(true)
|
|
|
|
|
r.isReachable(nodeD) should be(true)
|
|
|
|
|
r.isReachable(nodeE) should be(false)
|
2013-08-27 15:14:53 +02:00
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
r.allUnreachable should be(Set(nodeA, nodeB))
|
|
|
|
|
r.allUnreachableFrom(nodeA) should be(Set(nodeE))
|
|
|
|
|
r.allUnreachableFrom(nodeB) should be(Set(nodeA))
|
|
|
|
|
r.allUnreachableFrom(nodeC) should be(Set(nodeA))
|
|
|
|
|
r.allUnreachableFrom(nodeD) should be(Set(nodeA, nodeB))
|
2013-08-27 15:14:53 +02:00
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
r.observersGroupedByUnreachable should be(Map(
|
2013-08-27 15:14:53 +02:00
|
|
|
nodeA -> Set(nodeB, nodeC, nodeD),
|
|
|
|
|
nodeB -> Set(nodeD),
|
|
|
|
|
nodeE -> Set(nodeA)))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"merge by picking latest version of each record" in {
|
|
|
|
|
val r1 = Reachability.empty.unreachable(nodeB, nodeA).unreachable(nodeC, nodeD)
|
|
|
|
|
val r2 = r1.reachable(nodeB, nodeA).unreachable(nodeD, nodeE).unreachable(nodeC, nodeA)
|
|
|
|
|
val merged = r1.merge(Set(nodeA, nodeB, nodeC, nodeD, nodeE), r2)
|
|
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
merged.status(nodeB, nodeA) should be(Reachable)
|
|
|
|
|
merged.status(nodeC, nodeA) should be(Unreachable)
|
|
|
|
|
merged.status(nodeC, nodeD) should be(Unreachable)
|
|
|
|
|
merged.status(nodeD, nodeE) should be(Unreachable)
|
|
|
|
|
merged.status(nodeE, nodeA) should be(Reachable)
|
2013-08-27 15:14:53 +02:00
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
merged.isReachable(nodeA) should be(false)
|
|
|
|
|
merged.isReachable(nodeD) should be(false)
|
|
|
|
|
merged.isReachable(nodeE) should be(false)
|
2013-08-27 15:14:53 +02:00
|
|
|
|
|
|
|
|
val merged2 = r2.merge(Set(nodeA, nodeB, nodeC, nodeD, nodeE), r1)
|
2013-12-17 14:25:56 +01:00
|
|
|
merged2.records.toSet should be(merged.records.toSet)
|
2013-08-27 15:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
2013-09-12 17:19:53 +02:00
|
|
|
"merge by taking allowed set into account" in {
|
|
|
|
|
val r1 = Reachability.empty.unreachable(nodeB, nodeA).unreachable(nodeC, nodeD)
|
|
|
|
|
val r2 = r1.reachable(nodeB, nodeA).unreachable(nodeD, nodeE).unreachable(nodeC, nodeA)
|
|
|
|
|
// nodeD not in allowed set
|
|
|
|
|
val allowed = Set(nodeA, nodeB, nodeC, nodeE)
|
|
|
|
|
val merged = r1.merge(allowed, r2)
|
|
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
merged.status(nodeB, nodeA) should be(Reachable)
|
|
|
|
|
merged.status(nodeC, nodeA) should be(Unreachable)
|
|
|
|
|
merged.status(nodeC, nodeD) should be(Reachable)
|
|
|
|
|
merged.status(nodeD, nodeE) should be(Reachable)
|
|
|
|
|
merged.status(nodeE, nodeA) should be(Reachable)
|
2013-09-12 17:19:53 +02:00
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
merged.isReachable(nodeA) should be(false)
|
|
|
|
|
merged.isReachable(nodeD) should be(true)
|
|
|
|
|
merged.isReachable(nodeE) should be(true)
|
2013-09-12 17:19:53 +02:00
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
merged.versions.keySet should be(Set(nodeB, nodeC))
|
2013-09-12 17:19:53 +02:00
|
|
|
|
|
|
|
|
val merged2 = r2.merge(allowed, r1)
|
2013-12-17 14:25:56 +01:00
|
|
|
merged2.records.toSet should be(merged.records.toSet)
|
|
|
|
|
merged2.versions should be(merged.versions)
|
2013-09-12 17:19:53 +02:00
|
|
|
}
|
|
|
|
|
|
2013-08-27 15:14:53 +02:00
|
|
|
"merge correctly after pruning" in {
|
|
|
|
|
val r1 = Reachability.empty.unreachable(nodeB, nodeA).unreachable(nodeC, nodeD)
|
|
|
|
|
val r2 = r1.unreachable(nodeA, nodeE)
|
|
|
|
|
val r3 = r1.reachable(nodeB, nodeA) // nodeB pruned
|
|
|
|
|
val merged = r2.merge(Set(nodeA, nodeB, nodeC, nodeD, nodeE), r3)
|
|
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
merged.records.toSet should be(Set(
|
2013-08-27 15:14:53 +02:00
|
|
|
Record(nodeA, nodeE, Unreachable, 1),
|
|
|
|
|
Record(nodeC, nodeD, Unreachable, 1)))
|
|
|
|
|
|
|
|
|
|
val merged3 = r3.merge(Set(nodeA, nodeB, nodeC, nodeD, nodeE), r2)
|
2013-12-17 14:25:56 +01:00
|
|
|
merged3.records.toSet should be(merged.records.toSet)
|
2013-08-27 15:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"merge versions correctly" in {
|
|
|
|
|
val r1 = Reachability(Vector.empty, Map(nodeA -> 3L, nodeB -> 5L, nodeC -> 7L))
|
|
|
|
|
val r2 = Reachability(Vector.empty, Map(nodeA -> 6L, nodeB -> 2L, nodeD -> 1L))
|
|
|
|
|
val merged = r1.merge(Set(nodeA, nodeB, nodeC, nodeD, nodeE), r2)
|
|
|
|
|
|
|
|
|
|
val expected = Map(nodeA -> 6L, nodeB -> 5L, nodeC -> 7L, nodeD -> 1L)
|
2013-12-17 14:25:56 +01:00
|
|
|
merged.versions should be(expected)
|
2013-08-27 15:14:53 +02:00
|
|
|
|
|
|
|
|
val merged2 = r2.merge(Set(nodeA, nodeB, nodeC, nodeD, nodeE), r1)
|
2013-12-17 14:25:56 +01:00
|
|
|
merged2.versions should be(expected)
|
2013-08-27 15:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
"remove node" in {
|
|
|
|
|
val r = Reachability.empty.
|
|
|
|
|
unreachable(nodeB, nodeA).
|
|
|
|
|
unreachable(nodeC, nodeD).
|
|
|
|
|
unreachable(nodeB, nodeC).
|
|
|
|
|
unreachable(nodeB, nodeE).
|
|
|
|
|
remove(Set(nodeA, nodeB))
|
|
|
|
|
|
2013-12-17 14:25:56 +01:00
|
|
|
r.status(nodeB, nodeA) should be(Reachable)
|
|
|
|
|
r.status(nodeC, nodeD) should be(Unreachable)
|
|
|
|
|
r.status(nodeB, nodeC) should be(Reachable)
|
|
|
|
|
r.status(nodeB, nodeE) should be(Reachable)
|
2013-08-27 15:14:53 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
}
|