pekko/akka-persistence/src/test/scala/akka/persistence/SnapshotSpec.scala

258 lines
9.8 KiB
Scala

/*
* Copyright (C) 2009-2018 Lightbend Inc. <https://www.lightbend.com>
*/
package akka.persistence
import akka.actor._
import akka.testkit._
object SnapshotSpec {
case object TakeSnapshot
class SaveSnapshotTestPersistentActor(name: String, probe: ActorRef) extends NamedPersistentActor(name) {
var state = List.empty[String]
override def receiveRecover: Receive = {
case payload: String state = s"${payload}-${lastSequenceNr}" :: state
case SnapshotOffer(_, snapshot: List[_]) state = snapshot.asInstanceOf[List[String]]
}
override def receiveCommand = {
case payload: String
persist(payload) { _
state = s"${payload}-${lastSequenceNr}" :: state
}
case TakeSnapshot saveSnapshot(state)
case SaveSnapshotSuccess(md) probe ! md.sequenceNr
case GetState probe ! state.reverse
}
}
class LoadSnapshotTestPersistentActor(name: String, _recovery: Recovery, probe: ActorRef) extends NamedPersistentActor(name) {
override def recovery: Recovery = _recovery
override def receiveRecover: Receive = {
case payload: String probe ! s"${payload}-${lastSequenceNr}"
case offer @ SnapshotOffer(md, s) probe ! offer
case other probe ! other
}
override def receiveCommand = {
case "done" probe ! "done"
case payload: String
persist(payload) { _
probe ! s"${payload}-${lastSequenceNr}"
}
case offer @ SnapshotOffer(md, s) probe ! offer
case other probe ! other
}
}
class IgnoringSnapshotTestPersistentActor(name: String, _recovery: Recovery, probe: ActorRef) extends NamedPersistentActor(name) {
override def recovery: Recovery = _recovery
override def receiveRecover: Receive = {
case payload: String probe ! s"${payload}-${lastSequenceNr}"
case other if !other.isInstanceOf[SnapshotOffer] probe ! other
}
override def receiveCommand = {
case "done" probe ! "done"
case payload: String
persist(payload) { _
probe ! s"${payload}-${lastSequenceNr}"
}
case other probe ! other
}
}
final case class Delete1(metadata: SnapshotMetadata)
final case class DeleteN(criteria: SnapshotSelectionCriteria)
class DeleteSnapshotTestPersistentActor(name: String, _recovery: Recovery, probe: ActorRef) extends LoadSnapshotTestPersistentActor(name, _recovery, probe) {
override def receiveCommand = receiveDelete orElse super.receiveCommand
def receiveDelete: Receive = {
case Delete1(metadata) deleteSnapshot(metadata.sequenceNr)
case DeleteN(criteria) deleteSnapshots(criteria)
}
}
}
class SnapshotSpec extends PersistenceSpec(PersistenceSpec.config("leveldb", "SnapshotSpec")) with ImplicitSender {
import SnapshotSpec._
import SnapshotProtocol._
override protected def beforeEach(): Unit = {
super.beforeEach()
val persistentActor = system.actorOf(Props(classOf[SaveSnapshotTestPersistentActor], name, testActor))
persistentActor ! "a"
persistentActor ! TakeSnapshot
persistentActor ! "b"
persistentActor ! TakeSnapshot
persistentActor ! "c"
persistentActor ! "d"
persistentActor ! TakeSnapshot
persistentActor ! "e"
persistentActor ! "f"
expectMsgAllOf(1L, 2L, 4L)
}
"A persistentActor" must {
"recover state starting from the most recent snapshot" in {
val persistentActor = system.actorOf(Props(classOf[LoadSnapshotTestPersistentActor], name, Recovery(), testActor))
val persistenceId = name
expectMsgPF() {
case SnapshotOffer(SnapshotMetadata(`persistenceId`, 4, timestamp), state)
state should ===(List("a-1", "b-2", "c-3", "d-4").reverse)
timestamp should be > (0L)
}
expectMsg("e-5")
expectMsg("f-6")
expectMsg(RecoveryCompleted)
}
"recover completely if snapshot is not handled" in {
val persistentActor = system.actorOf(Props(classOf[IgnoringSnapshotTestPersistentActor], name, Recovery(), testActor))
val persistenceId = name
expectMsg("a-1")
expectMsg("b-2")
expectMsg("c-3")
expectMsg("d-4")
expectMsg("e-5")
expectMsg("f-6")
expectMsg(RecoveryCompleted)
}
"recover state starting from the most recent snapshot matching an upper sequence number bound" in {
val persistentActor = system.actorOf(Props(classOf[LoadSnapshotTestPersistentActor], name, Recovery(toSequenceNr = 3), testActor))
val persistenceId = name
expectMsgPF() {
case SnapshotOffer(SnapshotMetadata(`persistenceId`, 2, timestamp), state)
state should ===(List("a-1", "b-2").reverse)
timestamp should be > (0L)
}
expectMsg("c-3")
expectMsg(RecoveryCompleted)
}
"recover state starting from the most recent snapshot matching an upper sequence number bound (without further replay)" in {
val persistentActor = system.actorOf(Props(classOf[LoadSnapshotTestPersistentActor], name, Recovery(toSequenceNr = 4), testActor))
val persistenceId = name
persistentActor ! "done"
expectMsgPF() {
case SnapshotOffer(SnapshotMetadata(`persistenceId`, 4, timestamp), state)
state should ===(List("a-1", "b-2", "c-3", "d-4").reverse)
timestamp should be > (0L)
}
expectMsg(RecoveryCompleted)
expectMsg("done")
}
"recover state starting from the most recent snapshot matching criteria" in {
val recovery = Recovery(fromSnapshot = SnapshotSelectionCriteria(maxSequenceNr = 2))
val persistentActor = system.actorOf(Props(classOf[LoadSnapshotTestPersistentActor], name, recovery, testActor))
val persistenceId = name
expectMsgPF() {
case SnapshotOffer(SnapshotMetadata(`persistenceId`, 2, timestamp), state)
state should ===(List("a-1", "b-2").reverse)
timestamp should be > (0L)
}
expectMsg("c-3")
expectMsg("d-4")
expectMsg("e-5")
expectMsg("f-6")
expectMsg(RecoveryCompleted)
}
"recover state starting from the most recent snapshot matching criteria and an upper sequence number bound" in {
val recovery = Recovery(fromSnapshot = SnapshotSelectionCriteria(maxSequenceNr = 2), toSequenceNr = 3)
val persistentActor = system.actorOf(Props(classOf[LoadSnapshotTestPersistentActor], name, recovery, testActor))
val persistenceId = name
expectMsgPF() {
case SnapshotOffer(SnapshotMetadata(`persistenceId`, 2, timestamp), state)
state should ===(List("a-1", "b-2").reverse)
timestamp should be > (0L)
}
expectMsg("c-3")
expectMsg(RecoveryCompleted)
}
"recover state from scratch if snapshot based recovery is disabled" in {
val recovery = Recovery(fromSnapshot = SnapshotSelectionCriteria.None, toSequenceNr = 3)
val persistentActor = system.actorOf(Props(classOf[LoadSnapshotTestPersistentActor], name, recovery, testActor))
expectMsg("a-1")
expectMsg("b-2")
expectMsg("c-3")
expectMsg(RecoveryCompleted)
}
"support single snapshot deletions" in {
val deleteProbe = TestProbe()
// recover persistentActor from 3rd snapshot and then delete snapshot
val recovery = Recovery(toSequenceNr = 4)
val persistentActor1 = system.actorOf(Props(classOf[DeleteSnapshotTestPersistentActor], name, recovery, testActor))
val persistenceId = name
system.eventStream.subscribe(deleteProbe.ref, classOf[DeleteSnapshot])
persistentActor1 ! "done"
val metadata = expectMsgPF() {
case SnapshotOffer(md @ SnapshotMetadata(`persistenceId`, 4, _), state)
state should ===(List("a-1", "b-2", "c-3", "d-4").reverse)
md
}
expectMsg(RecoveryCompleted)
expectMsg("done")
persistentActor1 ! Delete1(metadata)
deleteProbe.expectMsgType[DeleteSnapshot]
expectMsgPF() { case m @ DeleteSnapshotSuccess(SnapshotMetadata(`persistenceId`, 4, _)) }
// recover persistentActor from 2nd snapshot (3rd was deleted) plus replayed messages
val persistentActor2 = system.actorOf(Props(classOf[DeleteSnapshotTestPersistentActor], name, recovery, testActor))
expectMsgPF(hint = "" + SnapshotOffer(SnapshotMetadata(`persistenceId`, 2, 0), null)) {
case SnapshotOffer(md @ SnapshotMetadata(`persistenceId`, 2, _), state)
state should ===(List("a-1", "b-2").reverse)
md
}
expectMsg("c-3")
expectMsg("d-4")
expectMsg(RecoveryCompleted)
}
"support bulk snapshot deletions" in {
val deleteProbe = TestProbe()
val recovery = Recovery(toSequenceNr = 4)
val persistentActor1 = system.actorOf(Props(classOf[DeleteSnapshotTestPersistentActor], name, recovery, testActor))
val persistenceId = name
system.eventStream.subscribe(deleteProbe.ref, classOf[DeleteSnapshots])
// recover persistentActor and the delete first three (= all) snapshots
val criteria = SnapshotSelectionCriteria(maxSequenceNr = 4)
persistentActor1 ! DeleteN(criteria)
expectMsgPF() {
case SnapshotOffer(md @ SnapshotMetadata(`persistenceId`, 4, _), state)
state should ===(List("a-1", "b-2", "c-3", "d-4").reverse)
}
expectMsg(RecoveryCompleted)
deleteProbe.expectMsgType[DeleteSnapshots]
expectMsgPF() { case DeleteSnapshotsSuccess(`criteria`) }
// recover persistentActor from replayed messages (all snapshots deleted)
val persistentActor2 = system.actorOf(Props(classOf[DeleteSnapshotTestPersistentActor], name, recovery, testActor))
expectMsg("a-1")
expectMsg("b-2")
expectMsg("c-3")
expectMsg("d-4")
expectMsg(RecoveryCompleted)
}
}
}