I am using akka and would like to test my actors. The test looks as follows:
import akka.testkit._
import com.sweetsoft._
import org.scalatest.Assertion
import com.typesafe.config.{Config, ConfigFactory}
import org.testcontainers.containers.KafkaContainer
import concurrent.duration._
final class DetectorSpec extends BddSpec {
private val sapMock = new SapMock()
.withExposedPorts(8080)
private val kafkaMock = new KafkaContainer()
private val config: String => String => Config = kafka => sap =>
ConfigFactory.parseString(
s"""
|kafka {
| servers = "$kafka"
|}
|
|sap {
| server = "ws://${sap}"
|}
|
""")
private val listener1 = TestProbe()
private val listener2 = TestProbe()
private val detector = system.actorOf(DetectorSupervisor.props)
after {
}
override def beforeAll(): Unit = {
}
override def afterAll(): Unit = {
TestKit.shutdownActorSystem(system)
}
feature("Detect Kafka and SAP availability") {
info("As a technical user, I want to be notified in real time, if Kafka and SAP is up and running or not.")
scenario("SAP and Kafka are offline") {
Given("I am registering two listeners")
detector ! AddNewListener(listener1.ref)
detector ! AddNewListener(listener2.ref)
When("I am receive the state message")
val res1 = listener1.expectMsgPF[Assertion](2.second) _
val res2 = listener2.expectMsgPF[Assertion](2.second) _
Then("it should contain `Kafka and SAP are offline`")
res1 {
case status: ServerStatus =>
status.health should be(ServersOffline)
}
res2 {
case status: ServerStatus =>
status.health should be(ServersOffline)
}
}
scenario("SAP is online and Kafka is offline") {
sapMock.start()
Given("I am waiting for the current state message")
detector ! AddNewListener(listener1.ref)
When("I am receive the state message")
val res1 = listener1.expectMsgPF[Assertion](2.second) _
Then("it should contain `Kafka is offline`")
res1 {
case status: ServerStatus =>
sapMock.stop()
status.health should be(ServersOffline)
}
}
scenario("SAP is offline and Kafka is online") {
Given("I am waiting for the current state message")
When("I am receive the state message")
Then("it should contain `SAP is offline`")
cancel()
}
scenario("SAP and Kafka are available") {
Given("I am waiting for the current state message")
When("I am receive the state message")
Then("it should contain `SAP and Kafka are online`")
cancel()
}
}
}
As you can see, I am using testcontainer to build the test environment.
I would like to start the container only on particular scenario and on the scenario, I have would like inject the configuration.
For example, the scenario scenario("SAP and Kafka are offline") has different configuration than scenario("SAP is online and Kafka is offline").
The question is, how to load different configuration on different scenario?
On the akka website, it shows how to load a configuration as follows:
import akka.actor.ActorSystem
import com.typesafe.config.ConfigFactory
val customConf = ConfigFactory.parseString("""
akka.actor.deployment {
/my-service {
router = round-robin-pool
nr-of-instances = 3
}
}
""")
// ConfigFactory.load sandwiches customConfig between default reference
// config and default overrides, and then resolves it.
val system = ActorSystem("MySystem", ConfigFactory.load(customConf))
Why do I have to do it in this way, because the container port is only available, when the container is started and I do not want to start the container on every scenario.
The BddSpec class:
abstract class BddSpec extends TestKit(ActorSystem("PluggerSystem"))
with AsyncFeatureSpecLike
with Matchers
with GivenWhenThen
with BeforeAndAfter
with BeforeAndAfterAll
Related
How can I use loan-fixture method with AsyncFeatureSpec?
I have tried as the following:
import akka.actor._
import akka.testkit._
import com.sweetsoft._
import org.scalatest._
import scala.concurrent.duration._
class SapOnlineKafkaOnlineSpec extends fixture.AsyncFeatureSpec with Matchers
with GivenWhenThen
with BeforeAndAfter
with BeforeAndAfterAll {
private val listener1 = TestProbe()
private val detector = system.actorOf(DetectorSupervisor.props)
def withKafkaAndSap(testCode: (ActorRef) => Any) {
}
feature("Detect Kafka and SAP availability") {
info("As a technical user, I want to be notified in real time, if Kafka and SAP is up and running or not.")
scenario("SAP and Kafka are available") in withKafkaAndSap { (listener) =>
Given("I am waiting for the current state message")
detector ! AddNewListener(listener1.ref)
When("I am receive the state message")
val res1 = listener1.expectMsgPF[Assertion](6.second) _
Then("it should contain `SAP and Kafka are online`")
res1 {
case status: ServerStatus =>
status.health should be(ServersOnline)
}
}
}
}
I do not know, how to inject the loan method.
Try the following
class SapOnlineKafkaOnlineSpec extends AsyncFeatureSpec with Matchers
with GivenWhenThen
with BeforeAndAfter
with BeforeAndAfterAll {
def withKafkaAndSap(testCode: ActorRef => Future[Assertion]) = {
val actor = ...
testCode(actor)
}
feature("Detect Kafka and SAP availability") {
info("As a technical user, I want to be notified in real time, if Kafka and SAP is up and running or not.")
scenario("SAP and Kafka are available") {
withKafkaAndSap { listner =>
Given("I am waiting for the current state message")
When("I am receive the state message")
Then("it should contain `SAP and Kafka are online`")
succeed
}
}
}
}
Note the loan fixture parameter type should be
testCode: ActorRef => Future[Assertion]
instead of ActorRef => Any, and we extend just AsyncFeatureSpec instead of fixture.AsyncFeatureSpec
I have an actor like this
class TcpClientActor(target: Target) extends Actor with Logger {
override def preStart(): Unit = {
self ! TestConnection
}
override def receive: Receive = {
case TestConnection =>
IO(Tcp) ! Connect(remoteAddress = new InetSocketAddress(target.endpoint, target.port), localAddress = None, options = Nil, timeout = Some(timeout), pullMode = false)
case failed#CommandFailed(_: Connect) =>
info(s"Failure: $target.endpoint:$target.port")
shutdown()
case Connected(_, _) =>
info(s"Success: $target.endpoint:$target.port")
sender() ! Close
shutdown()
}
def shutdown(): Unit = {
context stop self
}
}
I'm iterating over a file with endpoints to test against and creating one of these actors with each line as a constructor argument of type Target. I want to be able to throttle the number of parallel TCP connections to initiate to some set number, are there built-in mechanisms I can use in Akka to ensure I don't overload the system by just immediately creating a TcpClientActor for every line of input and kicking off a socket connection?
I would use an Akka Stream to throttle the messages
import scala.concurrent.duration._
import akka.NotUsed
import akka.actor.ActorRef
import akka.stream.{ ActorMaterializer, OverflowStrategy, ThrottleMode }
import akka.stream.scaladsl.{ Sink, Source }
object TcpThrottle {
def throttler(ratePerSecond: Int, burstRate: Option[Int], bufferSize: Int = 1000)(implicit materializer: ActorMaterializer): ActorRef =
Source.actorRef(bufferSize = bufferSize, OverflowStrategy.dropNew)
.throttle(ratePerSecond, 1.second, burstRate.getOrElse(ratePerSecond), ThrottleMode.Shaping)
.to(Sink.actorRef(IO(Tcp), NotUsed)
.run()
}
class TcpClientActor(target: Target) extends Actor with Logger {
val throttler = TcpThrottle.throttler(1, Some(5))
// otherwise identical
def receive: Receive = {
case TestConnection => throttler ! Connect(remoteAddress = new InetSocketAddress(target.endpoint, target.port), localAddress = None, options = Nil, timeout = Some(timeout), pullMode = false)
// other cases identical
}
}
Adapted from The Akka 2.5 migration guide. It may be necessary
I'm just messing around with some basics to learn akka remoting, and I'm running into an issue where my proxy class sends Identify messages to my backend, but never receives a response back.
I've verified that the backend receives messages sent to the ActorSelection, and I've seen a logging message where the backend actor says it will use xxx for serialization of the ActorIdentity messages. Not sure where I'm getting it wrong.
Here is my proxy class:
package remoting
import akka.actor.{Actor, ActorIdentity, ActorRef, Identify, ReceiveTimeout, Terminated}
import com.typesafe.config.ConfigFactory
import scala.concurrent.duration._
class BackendProxyActor extends Actor {
context.setReceiveTimeout(3.seconds)
val path = createPath
val backendSelection = context.actorSelection(path)
println(f"BackendSelection is $backendSelection")
override def preStart(): Unit = backendSelection ! Identify(1)
override def receive: Receive = {
case ActorIdentity(1, Some(actor)) =>
context.setReceiveTimeout(Duration.Undefined)
context.watch(actor)
context.become(active(actor))
case ActorIdentity(1, None) =>
println("Backend actor not available")
case ReceiveTimeout =>
backendSelection ! Identify(1)
case msg: Any => println(f"Received $msg while identifying backend")
}
def active(backend: ActorRef): Receive = {
case msg: Any => backend ! msg
case Terminated(backend) =>
println("backend terminated, going to identifying state")
context.setReceiveTimeout(3.seconds)
context.become(receive)
}
def createPath: String = {
val config = ConfigFactory.load("frontend").getConfig("backend")
val name = config.getString("name")
val host = config.getString("host")
val port = config.getString("port")
val system = config.getString("system")
val protocol = config.getString("protocol")
f"$protocol://$system#$host:$port/$name"
}
}
Here is my backend class:
package remoting
import akka.actor.{Actor, Identify, PoisonPill}
import com.typesafe.config.ConfigFactory
class BackendActor extends Actor {
val config = ConfigFactory.load("backend")
override def receive: Receive = {
case "stop" => self ! PoisonPill
case msg: Any => println(f"Received $msg")
}
}
My frontend class:
package remoting
import akka.actor.{Actor, Props}
class FrontendActor extends Actor {
val proxy = context.actorOf(Props[BackendProxyActor], "backendProxy")
override def receive: Receive = {
case msg: Any => proxy ! msg
}
}
and finally my App class:
package remoting
import akka.actor.{ActorSystem, Props}
import com.typesafe.config.ConfigFactory
object Main extends App {
val frontendConfig = ConfigFactory.load("frontend")
val frontend = ActorSystem("frontend", frontendConfig)
val frontendActor = frontend.actorOf(Props[FrontendActor], "FrontendActor")
(1 to 20).foreach(i => frontendActor ! f"Msg #$i")
frontendActor ! "stop"
}
My backend is being started in another process, and is run on port 2551, while my frontend is on port 2552.
In Play 2.6 I'm using the following code in my controller to spin up a WebSocket actor:
def ws: WebSocket = WebSocket.accept[JsValue, JsValue] { request =>
ActorFlow.actorRef { out =>
WebSocketActor.props(request.id.toString, out)
}
}
Internally Play will create some sink actor and my actor (WebSocketActor) will be created as a child of that actor. The sink actor provides some default supervision strategy (Stop on an error), but I'd like to set my own strategy to restart WebSocketActor in the event of a failure. How can I do it?
Play's ActorFlow doesn't provide a way to override its supervisor strategy of stopping your actor when an exception is thrown. You could, however, simply define your own copy of ActorFlow with the strategy of your choice:
import akka.actor._
import akka.stream.{ Materializer, OverflowStrategy }
import akka.stream.scaladsl.{ Sink, Keep, Source, Flow }
object ActorFlowAlt {
def actorRef[In, Out](props: ActorRef => Props, bufferSize: Int = 16, overflowStrategy: OverflowStrategy = OverflowStrategy.dropNew)(implicit factory: ActorRefFactory, mat: Materializer): Flow[In, Out, _] = {
val (outActor, publisher) = Source.actorRef[Out](bufferSize, overflowStrategy)
.toMat(Sink.asPublisher(false))(Keep.both).run()
Flow.fromSinkAndSource(
Sink.actorRef(factory.actorOf(Props(new Actor {
val flowActor = context.watch(context.actorOf(props(outActor), "flowActor"))
def receive = {
case Status.Success(_) | Status.Failure(_) => flowActor ! PoisonPill
case Terminated(_) => context.stop(self)
case other => flowActor ! other
}
override def supervisorStrategy = OneForOneStrategy() {
case _ => SupervisorStrategy.Restart // <--- restart instead of stop
}
})), Status.Success(())),
Source.fromPublisher(publisher)
)
}
}
Then use this alternative in your controller:
def ws: WebSocket = WebSocket.accept[JsValue, JsValue] { request =>
ActorFlowAlt.actorRef { out =>
WebSocketActor.props(request.id.toString, out)
}
}
As far as I know, ActiveMQ sets delivery mode to PERSISTENT by default... so how do I set delivery mode to NON_PERSISTENT for a specific topic when using Akka-Camel? Here below is my sample code:
import akka.actor._
import akka.camel._
import org.apache.activemq.camel.component.ActiveMQComponent
case class MyMessage(body: String)
class MyProducer() extends Actor with Producer with Oneway {
def endpointUri: String = "activemq:MyTopic"
}
class SimpleConsumer() extends Actor with Consumer {
def endpointUri: String = "activemq:MyTopic"
def receive = {
case msg: CamelMessage => println(msg)
}
}
object MyApp extends App {
val actorSystem = ActorSystem("MyApp")
val system = CamelExtension(actorSystem)
system.context.addComponent(
"activemq",
ActiveMQComponent.activeMQComponent("nio://localhost:61616")
)
val consumer = actorSystem.actorOf(Props[MyConsumer])
val producer = actorSystem.actorOf(Props[MyProducer])
...
producer ! MyMessage("hello")
...
actorSystem.shutdown()
}
The options are set on the endpoint URI.
"activemq:MyTopic?deliveryPersistent=false"