* change package name to akka.cluster.sbr * reference.conf has same config paths * akka.cluster.sbr.SplitBrainResolverProvider instead of com.lightbend.akka.sbr.SplitBrainResolverProvider * dependency from akka-cluster to akka-coordination, for lease strategy * move TestLease to akka-coordination and use that in SBR tests * remove keep-referee strategy * use keep-majority by default * review and adjust reference documentation Co-authored-by: Johan Andrén <johan@markatta.com> Co-authored-by: Johannes Rudolph <johannes.rudolph@gmail.com> Co-authored-by: Christopher Batey <christopher.batey@gmail.com> Co-authored-by: Arnout Engelen <github@bzzt.net>
124 lines
3.6 KiB
Scala
124 lines
3.6 KiB
Scala
/*
|
|
* Copyright (C) 2019-2020 Lightbend Inc. <https://www.lightbend.com>
|
|
*/
|
|
|
|
package akka.cluster.sharding
|
|
|
|
import java.util.concurrent.atomic.AtomicInteger
|
|
|
|
import scala.concurrent.Future
|
|
import scala.concurrent.duration._
|
|
import scala.util.Success
|
|
import scala.util.control.NoStackTrace
|
|
|
|
import akka.actor.{ Actor, ActorLogging, PoisonPill, Props }
|
|
import akka.cluster.sharding.ShardRegion.ShardInitialized
|
|
import akka.coordination.lease.LeaseUsageSettings
|
|
import akka.coordination.lease.TestLease
|
|
import akka.coordination.lease.TestLeaseExt
|
|
import akka.testkit.{ AkkaSpec, ImplicitSender, TestProbe }
|
|
|
|
object ShardSpec {
|
|
val config =
|
|
s"""
|
|
akka.loglevel = INFO
|
|
akka.actor.provider = "cluster"
|
|
akka.remote.classic.netty.tcp.port = 0
|
|
akka.remote.artery.canonical.port = 0
|
|
test-lease {
|
|
lease-class = ${classOf[TestLease].getName}
|
|
heartbeat-interval = 1s
|
|
heartbeat-timeout = 120s
|
|
lease-operation-timeout = 3s
|
|
}
|
|
"""
|
|
|
|
class EntityActor extends Actor with ActorLogging {
|
|
override def receive: Receive = {
|
|
case msg =>
|
|
log.info("Msg {}", msg)
|
|
sender() ! s"ack ${msg}"
|
|
}
|
|
}
|
|
|
|
val numberOfShards = 5
|
|
|
|
case class EntityEnvelope(entityId: Int, msg: Any)
|
|
|
|
val extractEntityId: ShardRegion.ExtractEntityId = {
|
|
case EntityEnvelope(id, payload) => (id.toString, payload)
|
|
}
|
|
|
|
val extractShardId: ShardRegion.ExtractShardId = {
|
|
case EntityEnvelope(id, _) => (id % numberOfShards).toString
|
|
}
|
|
|
|
case class BadLease(msg: String) extends RuntimeException(msg) with NoStackTrace
|
|
}
|
|
|
|
class ShardSpec extends AkkaSpec(ShardSpec.config) with ImplicitSender {
|
|
|
|
import ShardSpec._
|
|
|
|
val shortDuration = 100.millis
|
|
val testLeaseExt = TestLeaseExt(system)
|
|
|
|
def leaseNameForShard(typeName: String, shardId: String) = s"${system.name}-shard-${typeName}-${shardId}"
|
|
|
|
"A Cluster Shard" should {
|
|
"not initialize the shard until the lease is acquired" in new Setup {
|
|
parent.expectNoMessage(shortDuration)
|
|
lease.initialPromise.complete(Success(true))
|
|
parent.expectMsg(ShardInitialized(shardId))
|
|
}
|
|
|
|
"retry if lease acquire returns false" in new Setup {
|
|
lease.initialPromise.complete(Success(false))
|
|
parent.expectNoMessage(shortDuration)
|
|
lease.setNextAcquireResult(Future.successful(true))
|
|
parent.expectMsg(ShardInitialized(shardId))
|
|
}
|
|
|
|
"retry if the lease acquire fails" in new Setup {
|
|
lease.initialPromise.failure(BadLease("no lease for you"))
|
|
parent.expectNoMessage(shortDuration)
|
|
lease.setNextAcquireResult(Future.successful(true))
|
|
parent.expectMsg(ShardInitialized(shardId))
|
|
}
|
|
|
|
"shutdown if lease is lost" in new Setup {
|
|
val probe = TestProbe()
|
|
probe.watch(shard)
|
|
lease.initialPromise.complete(Success(true))
|
|
parent.expectMsg(ShardInitialized(shardId))
|
|
lease.getCurrentCallback().apply(Some(BadLease("bye bye lease")))
|
|
probe.expectTerminated(shard)
|
|
}
|
|
}
|
|
|
|
val shardIds = new AtomicInteger(0)
|
|
def nextShardId = s"${shardIds.getAndIncrement()}"
|
|
|
|
trait Setup {
|
|
val shardId = nextShardId
|
|
val parent = TestProbe()
|
|
val settings = ClusterShardingSettings(system).withLeaseSettings(new LeaseUsageSettings("test-lease", 2.seconds))
|
|
def lease = awaitAssert {
|
|
testLeaseExt.getTestLease(leaseNameForShard(typeName, shardId))
|
|
}
|
|
|
|
val typeName = "type1"
|
|
val shard = parent.childActorOf(
|
|
Shard.props(
|
|
typeName,
|
|
shardId,
|
|
_ => Props(new EntityActor()),
|
|
settings,
|
|
extractEntityId,
|
|
extractShardId,
|
|
PoisonPill,
|
|
system.deadLetters,
|
|
1))
|
|
}
|
|
|
|
}
|