pekko/akka-distributed-data/src/test/scala/akka/cluster/ddata/ORMapSpec.scala
2017-03-17 02:47:50 +01:00

560 lines
23 KiB
Scala

/**
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
*/
package akka.cluster.ddata
import akka.actor.Address
import akka.cluster.UniqueAddress
import akka.cluster.ddata.Replicator.Changed
import org.scalatest.Matchers
import org.scalatest.WordSpec
class ORMapSpec extends WordSpec with Matchers {
val node1 = UniqueAddress(Address("akka.tcp", "Sys", "localhost", 2551), 1)
val node2 = UniqueAddress(node1.address.copy(port = Some(2552)), 2)
"A ORMap" must {
"be able to add entries" in {
val m = ORMap().put(node1, "a", GSet() + "A").put(node1, "b", GSet() + "B")
val GSet(a) = m.entries("a")
a should be(Set("A"))
val GSet(b) = m.entries("b")
b should be(Set("B"))
val m2 = m.put(node1, "a", GSet() + "C")
val GSet(a2) = m2.entries("a")
a2 should be(Set("C"))
}
"be able to add entries with deltas" in {
val m = ORMap().put(node1, "a", GSet() + "A").put(node1, "b", GSet() + "B")
val md = m.delta.get
val m1 = ORMap().mergeDelta(md)
val GSet(a) = m1.entries("a")
a should be(Set("A"))
val GSet(b) = m1.entries("b")
b should be(Set("B"))
val m2 = m1.put(node1, "a", GSet() + "C")
val GSet(a2) = m2.entries("a")
a2 should be(Set("C"))
}
"be able to remove entry" in {
val m = ORMap().put(node1, "a", GSet() + "A").put(node1, "b", GSet() + "B").remove(node1, "a")
m.entries.keySet should not contain ("a")
m.entries.keySet should contain("b")
}
"be able to remove entry using a delta" in {
val m = ORMap().put(node1, "a", GSet() + "A").put(node1, "b", GSet() + "B")
val addDelta = m.delta.get
val removeDelta = m.resetDelta.remove(node1, "a").delta.get
val m1 = ORMap().mergeDelta(addDelta)
m1.entries.keySet should contain("a")
val m2 = m1.mergeDelta(removeDelta)
m2.entries.keySet should not contain ("a")
m2.entries.keySet should contain("b")
}
"be able to add removed" in {
val m = ORMap().put(node1, "a", GSet() + "A").put(node1, "b", GSet() + "B").remove(node1, "a")
m.entries.keySet should not contain ("a")
m.entries.keySet should contain("b")
val m2 = m.put(node1, "a", GSet() + "C")
m2.entries.keySet should contain("a")
m2.entries.keySet should contain("b")
}
"be able to have its entries correctly merged with another ORMap with other entries" in {
val m1 = ORMap().put(node1, "a", GSet() + "A").put(node1, "b", GSet() + "B")
val m2 = ORMap().put(node2, "c", GSet() + "C")
// merge both ways
val merged1 = m1 merge m2
merged1.entries.keySet should contain("a")
merged1.entries.keySet should contain("b")
merged1.entries.keySet should contain("c")
val merged2 = m2 merge m1
merged2.entries.keySet should contain("a")
merged2.entries.keySet should contain("b")
merged2.entries.keySet should contain("c")
}
"be able to have its entries correctly merged with another ORMap with overlapping entries" in {
val m1 = ORMap().put(node1, "a", GSet() + "A1").put(node1, "b", GSet() + "B1").
remove(node1, "a").put(node1, "d", GSet() + "D1")
val m2 = ORMap().put(node2, "c", GSet() + "C2").put(node2, "a", GSet() + "A2").
put(node2, "b", GSet() + "B2").remove(node2, "b").put(node2, "d", GSet() + "D2")
// merge both ways
val merged1 = m1 merge m2
merged1.entries.keySet should contain("a")
val GSet(a1) = merged1.entries("a")
a1 should be(Set("A2"))
merged1.entries.keySet should contain("b")
val GSet(b1) = merged1.entries("b")
b1 should be(Set("B1"))
merged1.entries.keySet should contain("c")
merged1.entries.keySet should contain("d")
val GSet(d1) = merged1.entries("d")
d1 should be(Set("D1", "D2"))
val merged2 = m2 merge m1
merged2.entries.keySet should contain("a")
val GSet(a2) = merged1.entries("a")
a2 should be(Set("A2"))
merged2.entries.keySet should contain("b")
val GSet(b2) = merged2.entries("b")
b2 should be(Set("B1"))
merged2.entries.keySet should contain("c")
merged2.entries.keySet should contain("d")
val GSet(d2) = merged2.entries("d")
d2 should be(Set("D1", "D2"))
}
"illustrate the danger of using remove+put to replace an entry" in {
val m1 = ORMap.empty.put(node1, "a", GSet.empty + "A").put(node1, "b", GSet.empty + "B")
val m2 = ORMap.empty.put(node2, "c", GSet.empty + "C")
val merged1 = m1 merge m2
val m3 = merged1.remove(node1, "b").put(node1, "b", GSet.empty + "B2")
// same thing if only put is used
// val m3 = merged1.put(node1, "b", GSet.empty + "B2")
val merged2 = merged1 merge m3
merged2.entries("a").elements should be(Set("A"))
// note that B is included, because GSet("B") is merged with GSet("B2")
merged2.entries("b").elements should be(Set("B", "B2"))
merged2.entries("c").elements should be(Set("C"))
}
"not have anomalies for remove+updated scenario and deltas" in {
val m1 = ORMap.empty.put(node1, "a", GSet.empty + "A").put(node1, "b", GSet.empty + "B")
val m2 = ORMap.empty.put(node2, "c", GSet.empty + "C")
val merged1 = m1 merge m2
val m3 = merged1.resetDelta.remove(node1, "b")
val m4 = merged1.resetDelta.updated(node1, "b", GSet.empty[String])(_.add("B2"))
val merged2 = m3 merge m4
merged2.entries("a").elements should be(Set("A"))
// note that B is included, because GSet("B") is merged with GSet("B2")
merged2.entries("b").elements should be(Set("B", "B2"))
merged2.entries("c").elements should be(Set("C"))
val merged3 = m3 mergeDelta m4.delta.get
merged3.entries("a").elements should be(Set("A"))
// note that B is included, because GSet("B") is merged with GSet("B2")
merged3.entries("b").elements should be(Set("B", "B2"))
merged3.entries("c").elements should be(Set("C"))
}
"not have anomalies for remove+updated scenario and deltas 2" in {
val m1 = ORMap.empty.put(node1, "a", ORSet.empty.add(node1, "A")).put(node1, "b", ORSet.empty.add(node1, "B"))
val m2 = ORMap.empty.put(node2, "c", ORSet.empty.add(node2, "C"))
val merged1 = m1 merge m2
val m3 = merged1.resetDelta.remove(node1, "b")
val m4 = merged1.resetDelta.remove(node1, "b").updated(node1, "b", ORSet.empty[String])(_.add(node1, "B2"))
val merged2 = m3 merge m4
merged2.entries("a").elements should be(Set("A"))
// note that B is not included, because it was removed in both timelines
merged2.entries("b").elements should be(Set("B2"))
merged2.entries("c").elements should be(Set("C"))
val merged3 = m3 mergeDelta m4.delta.get
merged3.entries("a").elements should be(Set("A"))
// note that B is not included, because it was removed in both timelines
merged3.entries("b").elements should be(Set("B2"))
merged3.entries("c").elements should be(Set("C"))
}
"not have anomalies for remove+updated scenario and deltas 3" in {
val m1 = ORMap.empty.put(node1, "a", ORSet.empty.add(node1, "A")).put(node1, "b", ORSet.empty.add(node1, "B"))
val m2 = ORMap.empty.put(node2, "c", ORSet.empty.add(node2, "C"))
val merged1 = m1 merge m2
val m3 = merged1.resetDelta.remove(node1, "b")
val m4 = merged1.resetDelta.remove(node2, "b").updated(node2, "b", ORSet.empty[String])(_.add(node2, "B2"))
val merged2 = m3 merge m4
merged2.entries("a").elements should be(Set("A"))
// note that B is not included, because it was removed in both timelines
merged2.entries("b").elements should be(Set("B2"))
merged2.entries("c").elements should be(Set("C"))
val merged3 = m3 mergeDelta m4.delta.get
merged3.entries("a").elements should be(Set("A"))
// note that B is not included, because it was removed in both timelines
merged3.entries("b").elements should be(Set("B2"))
merged3.entries("c").elements should be(Set("C"))
}
"not have anomalies for remove+updated scenario and deltas 4" in {
val m1 = ORMap.empty.put(node1, "a", ORSet.empty.add(node1, "A")).put(node1, "b", ORSet.empty.add(node1, "B"))
val m2 = ORMap.empty.put(node2, "c", ORSet.empty.add(node2, "C"))
val merged1 = m1 merge m2
val m3 = merged1.resetDelta.remove(node1, "b")
val m4 = merged1.resetDelta.updated(node1, "b", ORSet.empty[String])(_.add(node1, "B2"))
val merged2 = m3 merge m4
merged2.entries("a").elements should be(Set("A"))
// note that B is included, because ORSet("B") is merged with ORSet("B2")
merged2.entries("b").elements should be(Set("B", "B2"))
merged2.entries("c").elements should be(Set("C"))
val merged3 = m3 mergeDelta m4.delta.get
merged3.entries("a").elements should be(Set("A"))
// note that B is included, because ORSet("B") is merged with ORSet("B2")
merged3.entries("b").elements should be(Set("B", "B2"))
merged3.entries("c").elements should be(Set("C"))
}
"not have anomalies for remove+updated scenario and deltas 5" in {
val m1 = ORMap.empty.put(node1, "a", GSet.empty + "A").put(node1, "b", GSet.empty + "B")
val m2 = ORMap.empty.put(node2, "c", GSet.empty + "C")
val merged1 = m1 merge m2
val m3 = merged1.resetDelta.remove(node1, "b")
val m4 = merged1.resetDelta.put(node2, "b", GSet.empty + "B2")
val merged2 = m3 merge m4
merged2.entries("a").elements should be(Set("A"))
// note that B is not included, because it was removed in both timelines
merged2.entries("b").elements should be(Set("B2"))
merged2.entries("c").elements should be(Set("C"))
val merged3 = m3 mergeDelta m4.delta.get
merged3.entries("a").elements should be(Set("A"))
// note that B is not included, because it was removed in both timelines
merged3.entries("b").elements should be(Set("B2"))
merged3.entries("c").elements should be(Set("C"))
}
"not have anomalies for remove+updated scenario and deltas 6" in {
val m1 = ORMap.empty.put(node1, "a", ORSet.empty.add(node1, "A")).put(node1, "b", ORSet.empty.add(node1, "B"))
val m2 = ORMap.empty.put(node2, "b", ORSet.empty.add(node2, "B3"))
val merged1 = m1 merge m2
val m3 = merged1.resetDelta.remove(node1, "b")
val m4 = merged1.resetDelta.remove(node2, "b").updated(node2, "b", ORSet.empty[String])(_.add(node2, "B1"))
.updated(node2, "b", ORSet.empty[String])(_.add(node2, "B2"))
val merged2 = m3 merge m4
merged2.entries("a").elements should be(Set("A"))
// note that B is not included, because it was removed in both timelines
merged2.entries("b").elements should be(Set("B1", "B2"))
val merged3 = m3 mergeDelta m4.delta.get
merged3.entries("a").elements should be(Set("A"))
// note that B is not included, because it was removed in both timelines
merged3.entries("b").elements should be(Set("B1", "B2"))
}
"not have anomalies for remove+updated scenario and deltas 7" in {
val m1 = ORMap.empty.put(node1, "a", ORSet.empty.add(node1, "A"))
.put(node1, "b", ORSet.empty.add(node1, "B1")).remove(node1, "b")
val m2 = ORMap.empty.put(node1, "a", ORSet.empty.add(node1, "A")).put(node1, "b", ORSet.empty.add(node1, "B2"))
val m2d = m2.resetDelta.remove(node1, "b")
val m2u = m2.resetDelta.updated(node1, "b", ORSet.empty[String])(_.add(node1, "B3"))
.updated(node2, "b", ORSet.empty[String])(_.add(node2, "B4"))
val merged1 = (m1 merge m2d) mergeDelta m2u.delta.get
merged1.entries("a").elements should be(Set("A"))
// note that B1 is lost as it was added and removed earlier in timeline than B2
merged1.entries("b").elements should be(Set("B2", "B3", "B4"))
}
"not have anomalies for remove+updated scenario and deltas 8" in {
val m1 = ORMap.empty.put(node1, "a", GSet.empty + "A")
.put(node1, "b", GSet.empty + "B").put(node2, "b", GSet.empty + "B")
val m2 = ORMap.empty.put(node2, "c", GSet.empty + "C")
val merged1 = m1 merge m2
val m3 = merged1.resetDelta.remove(node1, "b").remove(node2, "b")
val m4 = merged1.resetDelta.put(node2, "b", GSet.empty + "B2").put(node2, "b", GSet.empty + "B3")
val merged2 = m3 merge m4
merged2.entries("a").elements should be(Set("A"))
merged2.entries("b").elements should be(Set("B3"))
merged2.entries("c").elements should be(Set("C"))
val merged3 = (merged1 mergeDelta m3.delta.get) mergeDelta m4.delta.get
merged3.entries("a").elements should be(Set("A"))
merged3.entries("b").elements should be(Set("B3"))
merged3.entries("c").elements should be(Set("C"))
}
"not have anomalies for remove+updated scenario and deltas 9" in {
val m1 = ORMap.empty.put(node1, "a", GSet.empty + "A")
.put(node1, "b", GSet.empty + "B").put(node2, "b", GSet.empty + "B")
val m2 = ORMap.empty.put(node2, "c", GSet.empty + "C")
val merged1 = m1 merge m2
val m3 = merged1.resetDelta.remove(node1, "b").remove(node2, "b")
val m4 = merged1.resetDelta.updated(node2, "b", GSet.empty[String])(_.add("B2"))
.updated(node2, "b", GSet.empty[String])(_.add("B3"))
val merged2 = m3 merge m4
merged2.entries("a").elements should be(Set("A"))
merged2.entries("b").elements should be(Set("B2", "B3"))
merged2.entries("c").elements should be(Set("C"))
val merged3 = (merged1 mergeDelta m3.delta.get) mergeDelta m4.delta.get
merged3.entries("a").elements should be(Set("A"))
merged3.entries("b").elements should be(Set("B2", "B3"))
merged3.entries("c").elements should be(Set("C"))
}
"not have anomalies for remove+updated scenario and deltas 10" in {
val m1 = ORMap.empty.put(node2, "a", GSet.empty + "A")
.put(node2, "b", GSet.empty + "B")
val m3 = m1.resetDelta.remove(node2, "b")
val m4 = m3.resetDelta.put(node2, "b", GSet.empty + "B2").updated(node2, "b", GSet.empty[String])(_.add("B3"))
val merged2 = m3 merge m4
merged2.entries("a").elements should be(Set("A"))
merged2.entries("b").elements should be(Set("B2", "B3"))
val merged3 = m3 mergeDelta m4.delta.get
merged3.entries("a").elements should be(Set("A"))
merged3.entries("b").elements should be(Set("B2", "B3"))
}
"have the usual anomalies for remove+updated scenario" in {
// please note that the current ORMultiMap has the same anomaly
// because the condition of keeping global vvector is violated
// by removal of the whole entry for the removed key "b" which results in removal of it's value's vvector
val m1 = ORMap.empty.put(node1, "a", ORSet.empty.add(node1, "A")).put(node1, "b", ORSet.empty.add(node1, "B"))
val m2 = ORMap.empty.put(node2, "c", ORSet.empty.add(node2, "C"))
// m1 - node1 gets the update from m2
val merged1 = m1 merge m2
// m2 - node2 gets the update from m1
val merged2 = m2 merge m1
// RACE CONDITION ahead!
val m3 = merged1.resetDelta.remove(node1, "b")
// let's imagine that m3 (node1) update gets propagated here (full state or delta - doesn't matter)
// and is in flight, but in the meantime, an element is being added somewhere else (m4 - node2)
// and the update is propagated before the update from node1 is merged
val m4 = merged2.resetDelta.updated(node2, "b", ORSet.empty[String])(_.add(node2, "B2"))
// and later merged on node1
val merged3 = m3 merge m4
// and the other way round...
val merged4 = m4 merge m3
// result - the element "B" is kept on both sides...
merged3.entries("a").elements should be(Set("A"))
merged3.entries("b").elements should be(Set("B", "B2"))
merged3.entries("c").elements should be(Set("C"))
merged4.entries("a").elements should be(Set("A"))
merged4.entries("b").elements should be(Set("B", "B2"))
merged4.entries("c").elements should be(Set("C"))
// but if the timing was slightly different, so that the update from node1
// would get merged just before update on node2:
val merged5 = (m2 merge m3).resetDelta.updated(node2, "b", ORSet.empty[String])(_.add(node2, "B2"))
// the update propagated ... and merged on node1:
val merged6 = m3 merge merged5
// then the outcome is different... because the vvector of value("b") was lost...
merged5.entries("a").elements should be(Set("A"))
// this time it's different...
merged5.entries("b").elements should be(Set("B2"))
merged5.entries("c").elements should be(Set("C"))
merged6.entries("a").elements should be(Set("A"))
// this time it's different...
merged6.entries("b").elements should be(Set("B2"))
merged6.entries("c").elements should be(Set("C"))
}
"work with deltas and updated for GSet elements type" in {
val m1 = ORMap.empty.put(node1, "a", GSet.empty + "A")
val m2 = m1.resetDelta.updated(node1, "a", GSet.empty[String])(_.add("B"))
val m3 = ORMap().mergeDelta(m1.delta.get).mergeDelta(m2.delta.get)
val GSet(d3) = m3.entries("a")
d3 should be(Set("A", "B"))
}
"work with deltas and updated for ORSet elements type" in {
val m1 = ORMap.empty.put(node1, "a", ORSet.empty.add(node1, "A"))
val m2 = m1.resetDelta.updated(node1, "a", ORSet.empty[String])(_.add(node1, "B"))
val m3 = ORMap().mergeDelta(m1.delta.get).mergeDelta(m2.delta.get)
val ORSet(d3) = m3.entries("a")
d3 should be(Set("A", "B"))
}
"work with aggregated deltas and updated for GSet elements type" in {
val m1 = ORMap.empty.put(node1, "a", GSet.empty + "A")
val m2 = m1.resetDelta.updated(node1, "a", GSet.empty[String])(_.add("B")).updated(node1, "a", GSet.empty[String])(_.add("C"))
val m3 = ORMap().mergeDelta(m1.delta.get).mergeDelta(m2.delta.get)
val GSet(d3) = m3.entries("a")
d3 should be(Set("A", "B", "C"))
}
"work with deltas and updated for GCounter elements type" in {
val m1 = ORMap.empty.put(node1, "a", GCounter.empty)
val m2 = m1.resetDelta.updated(node1, "a", GCounter.empty)(_.increment(node1, 10))
val m3 = m2.resetDelta.updated(node2, "a", GCounter.empty)(_.increment(node2, 10))
val m4 = ORMap().mergeDelta(m1.delta.get).mergeDelta(m2.delta.get).mergeDelta(m3.delta.get)
val GCounter(num) = m4.entries("a")
num should ===(20)
}
"work with deltas and updated for PNCounter elements type" in {
val m1 = ORMap.empty.put(node1, "a", PNCounter.empty)
val m2 = m1.resetDelta.updated(node1, "a", PNCounter.empty)(_.increment(node1, 10))
val m3 = m2.resetDelta.updated(node2, "a", PNCounter.empty)(_.decrement(node2, 10))
val m4 = ORMap().mergeDelta(m1.delta.get).mergeDelta(m2.delta.get).mergeDelta(m3.delta.get)
val PNCounter(num) = m4.entries("a")
num should ===(0)
}
"work with deltas and updated for Flag elements type" in {
val m1 = ORMap.empty.put(node1, "a", Flag(false))
val m2 = m1.resetDelta.updated(node1, "a", Flag.empty)(_.switchOn)
val m3 = ORMap().mergeDelta(m1.delta.get).mergeDelta(m2.delta.get)
val Flag(d3) = m3.entries("a")
d3 should be(true)
}
"not allow put for ORSet elements type" in {
val m = ORMap().put(node1, "a", ORSet().add(node1, "A"))
intercept[IllegalArgumentException] {
m.put(node1, "a", ORSet().add(node1, "B"))
}
}
"be able to update entry" in {
val m1 = ORMap.empty[String, ORSet[String]].put(node1, "a", ORSet.empty.add(node1, "A"))
.put(node1, "b", ORSet.empty.add(node1, "B01").add(node1, "B02").add(node1, "B03"))
val m2 = ORMap.empty[String, ORSet[String]].put(node2, "c", ORSet.empty.add(node2, "C"))
val merged1: ORMap[String, ORSet[String]] = m1 merge m2
val m3 = merged1.updated(node1, "b", ORSet.empty[String])(_.clear(node1).add(node1, "B2"))
val merged2 = merged1 merge m3
merged2.entries("a").elements should be(Set("A"))
merged2.entries("b").elements should be(Set("B2"))
merged2.entries("c").elements should be(Set("C"))
val m4 = merged1.updated(node2, "b", ORSet.empty[String])(_.add(node2, "B3"))
val merged3 = m3 merge m4
merged3.entries("a").elements should be(Set("A"))
merged3.entries("b").elements should be(Set("B2", "B3"))
merged3.entries("c").elements should be(Set("C"))
}
"be able to update ORSet entry with remove+put" in {
val m1 = ORMap.empty[String, ORSet[String]].put(node1, "a", ORSet.empty.add(node1, "A01"))
.updated(node1, "a", ORSet.empty[String])(_.add(node1, "A02"))
.updated(node1, "a", ORSet.empty[String])(_.add(node1, "A03"))
.put(node1, "b", ORSet.empty.add(node1, "B01").add(node1, "B02").add(node1, "B03"))
val m2 = ORMap.empty[String, ORSet[String]].put(node2, "c", ORSet.empty.add(node2, "C"))
val merged1 = m1 merge m2
// note that remove + put work because the new VersionVector version is incremented
// from a global counter
val m3 = merged1.remove(node1, "b").put(node1, "b", ORSet.empty.add(node1, "B2"))
val merged2 = merged1 merge m3
merged2.entries("a").elements should be(Set("A01", "A02", "A03"))
merged2.entries("b").elements should be(Set("B2"))
merged2.entries("c").elements should be(Set("C"))
val m4 = merged1.updated(node2, "b", ORSet.empty[String])(_.add(node2, "B3"))
val merged3 = m3 merge m4
merged3.entries("a").elements should be(Set("A01", "A02", "A03"))
merged3.entries("b").elements should be(Set("B2", "B3"))
merged3.entries("c").elements should be(Set("C"))
}
"be able to update ORSet entry with remove -> merge -> put" in {
val m1 = ORMap.empty.put(node1, "a", ORSet.empty.add(node1, "A"))
.put(node1, "b", ORSet.empty.add(node1, "B01").add(node1, "B02").add(node1, "B03"))
val m2 = ORMap.empty.put(node2, "c", ORSet.empty.add(node2, "C"))
val merged1 = m1 merge m2
val m3 = merged1.remove(node1, "b")
val merged2 = merged1 merge m3
merged2.entries("a").elements should be(Set("A"))
merged2.contains("b") should be(false)
merged2.entries("c").elements should be(Set("C"))
val m4 = merged2.put(node1, "b", ORSet.empty.add(node1, "B2"))
val m5 = merged2.updated(node2, "c", ORSet.empty[String])(_.add(node2, "C2"))
.put(node2, "b", ORSet.empty.add(node2, "B3"))
val merged3 = m5 merge m4
merged3.entries("a").elements should be(Set("A"))
merged3.entries("b").elements should be(Set("B2", "B3"))
merged3.entries("c").elements should be(Set("C", "C2"))
}
"have unapply extractor" in {
val m1 = ORMap.empty.put(node1, "a", Flag(true)).put(node2, "b", Flag(false))
val m2: ORMap[String, Flag] = m1
val ORMap(entries1) = m1
val entries2: Map[String, Flag] = entries1
Changed(ORMapKey[String, Flag]("key"))(m1) match {
case c @ Changed(ORMapKey("key"))
val ORMap(entries3) = c.dataValue
val entries4: Map[String, ReplicatedData] = entries3
entries4 should be(Map("a" Flag(true), "b" Flag(false)))
}
}
}
}