How can I test a publisher to a DistributedPubSub in Akka Cluster? - scala

I have an actor whose sole responsibility is to forward messages it receives from external interfaces (command-line, user, etc.) to appropriate topics. I want to test that it correctly publishes these messages.
I will need to create some dummy subscriber who will expect messages published to a certain topic and make assertions about the messages it receives.
Here's my code that I attempted to realize that:
Messages.scala
case class Foo(foo: String)
InterfaceForwardingActor.scala
import akka.actor.{Actor, ActorLogging}
import akka.cluster.pubsub.{DistributedPubSub, DistributedPubSubMediator}
import com.typesafe.config.Config
/** Actor responsible for forwarding stimuli external to the system.
* For instance, messages from the command-line interface or from a UI.
*
*/
class InterfaceForwardingActor extends Actor with ActorLogging {
import DistributedPubSubMediator.Publish
protected val mediator = DistributedPubSub(context.system).mediator
log.info(s"Hello from interface forwarder.")
final val topic = "info"
def receive = {
case foo: Foo => {
log.info("Forwarding a Foo message")
mediator ! Publish(topic, foo)
}
}
}
and the test code
InterfaceForwardingActorTest.scala
import akka.actor.{ActorSystem, Props}
import akka.cluster.client.ClusterClient.Publish
import akka.cluster.pubsub.DistributedPubSub
import akka.testkit.{ImplicitSender, TestKit, TestProbe}
import com.typesafe.config.ConfigFactory
import org.scalatest.{BeforeAndAfterAll, Matchers, WordSpecLike}
class InterfaceForwardingActorTest extends
TestKit(ActorSystem("InterfaceForwardingActorSpec")) with ImplicitSender with
WordSpecLike with Matchers with BeforeAndAfterAll {
override def afterAll {
TestKit.shutdownActorSystem(system)
}
"An InterfaceForwardingActor" must {
val interfaceForwardingActor = system.actorOf(Props[InterfaceForwardingActor])
val probe = TestProbe()
val mediator = DistributedPubSub(system).mediator
// subscribe the test probe to the "info" topic
mediator ! Publish("info", probe.ref)
"publish a Foo message" in {
val msg = Foo("test")
interfaceForwardingActor ! msg
probe.expectMsg(msg)
}
}
}
What I find is that the probe, which is subscribed to the info topic, doesn't receive the message within the default timeout period of 3 seconds and the assertion fails. The interesting part, however, is that I do see the log message stating that the interface forwarding actor is indeed forwarding a Foo message.
What am I doing wrong in my test?

The TestProbe should be subscribed to the topic in the the test code:
mediator ! Subscribe("info", probe.ref)
instead of
mediator ! Publish("info", probe.ref)
Documentation page of distributed pub-sub is here, for reference.

Related

Testing calls to an akka actor in Play

I have refactored a bunch of email sending code in a play application to do that asynchronously using an actor.
When I need to send an email, I now have an injection of an EmailActor and I call emailActor ? EmailRequest(from, to, ...) to send it.
My question is, how can I unit test that the actor is actually called ?
I read the documentation of Akka regarding tests, but it seems to me it focuses on testing the actor themselves, not their invocation, and it is not clear at all where I should start.
You can use a TestProbe and inject it into your email service. Check out this simple test case
import akka.actor.{ActorRef, ActorSystem}
import akka.pattern.ask
import akka.testkit.{TestKit, TestProbe}
import akka.util.Timeout
import org.scalatest.flatspec.AsyncFlatSpecLike
import scala.concurrent.Future
import scala.concurrent.duration._
class TestProbeActorExample
extends TestKit(ActorSystem("test"))
with AsyncFlatSpecLike {
class MyService(actorRef: ActorRef) {
def sendEmail(email: String): Future[Int] = {
implicit val timeout: Timeout = 1.second
(actorRef ? email).mapTo[Int]
}
}
it should "test an actor" in {
val testProbe = TestProbe()
val service = new MyService(testProbe.ref)
val statusCode = service.sendEmail("email")
testProbe.expectMsg("email")
testProbe.reply(10)
statusCode.map(r => assert(r == 10))
}
}
Please note if you use ask pattern you need to assert that a message has been received with expectMsg and then you have to send a reply back with reply

Limit number of messages sent within time interval

Using below code I'm attempting to limit the amount of messages send to an actor within a specified time frame. But the messages are not being throttled and are being sent as quickly as possible. The downstream actor just makes a http request to the Google home page.
The throttler code where I attempt to limit 3 messages to be sent within 3 seconds :
val throttler: ActorRef =
Source.actorRef(bufferSize = 1000, OverflowStrategy.dropNew)
.throttle(3, 1.second)
.to(Sink.actorRef(printer, NotUsed))
.run()
How can I limit the number of messages sent within loop :
for( a <- 1 to 10000){
// Create the 'greeter' actors
val howdyGreeter: ActorRef =
system.actorOf(Greeter.props(String.valueOf(a), printer), String.valueOf(a))
howdyGreeter ! RequestActor("RequestActor")
howdyGreeter ! Greet
}
to 3 per second ?
entire code :
//https://developer.lightbend.com/guides/akka-quickstart-scala/full-example.html
import akka.NotUsed
import akka.stream.{OverflowStrategy, ThrottleMode}
import akka.stream.scaladsl.{Sink, Source}
import org.apache.http.client.methods.HttpGet
import org.apache.http.entity.StringEntity
import org.apache.http.impl.client.DefaultHttpClient
import net.liftweb.json._
import net.liftweb.json.Serialization.write
import org.apache.http.util.EntityUtils
//import akka.contrib.throttle.TimerBasedThrottler
import akka.actor.{Actor, ActorLogging, ActorRef, ActorSystem, Props}
import scala.concurrent.duration._
import akka.NotUsed
import akka.actor.ActorRef
import akka.actor.ActorSystem
import akka.stream.ActorMaterializer
import akka.stream.OverflowStrategy
import akka.stream.ThrottleMode
import akka.stream.scaladsl.Sink
import akka.stream.scaladsl.Source
object Greeter {
def props(message: String, printerActor: ActorRef): Props = Props(new Greeter(message, printerActor))
final case class RequestActor(who: String)
case object Greet
}
class Greeter(message: String, printerActor: ActorRef) extends Actor {
import Greeter._
import Printer._
var greeting = ""
def receive = {
case RequestActor(who) =>
val get = new HttpGet("http://www.google.com")
val response = (new DefaultHttpClient).execute(get)
// val responseString = EntityUtils.toString(response.getEntity, "UTF-8")
// System.out.println(responseString)
greeting = String.valueOf(response.getStatusLine.getStatusCode)
println("message is "+message)
// greeting = message + ", " + who
case Greet =>
printerActor ! Greeting(greeting)
}
}
object Printer {
def props: Props = Props[Printer]
final case class Greeting(greeting: String)
}
class Printer extends Actor with ActorLogging {
import Printer._
def receive = {
case Greeting(greeting) =>
log.info("Greeting received (from " + sender() + "): " + greeting)
}
}
object AkkaQuickstart extends App {
import Greeter._
// Create the 'helloAkka' actor system
val system: ActorSystem = ActorSystem("helloAkka")
// Create the printer actor,this is also the target actor
val printer: ActorRef = system.actorOf(Printer.props, "printerActor")
implicit val materializer = ActorMaterializer.create(system)
val throttler: ActorRef =
Source.actorRef(bufferSize = 1000, OverflowStrategy.dropNew)
.throttle(3, 1.second)
.to(Sink.actorRef(printer, NotUsed))
.run()
//Create a new actor for each request thread
for( a <- 1 to 10000){
// Create the 'greeter' actors
val howdyGreeter: ActorRef =
system.actorOf(Greeter.props(String.valueOf(a), printer), String.valueOf(a))
howdyGreeter ! RequestActor("RequestActor")
howdyGreeter ! Greet
}
}
An actor cannot influence what other actors do, in particular it has no control over who puts messages in its mailbox and when — this is how the actor model works. An actor only gets to decide what to do with the messages it finds in its mailbox, and over this it has full control. It can for example drop them, send back error replies, buffer them, etc.
If you want throttling and back-pressure, I recommend not using Actors at all for this part, but only use Akka Streams. The code that generates your request messages should be a Source, not a for-loop. Which source is most appropriate depends entirely on your real use-case, e.g. creating a stream from a strict collection with Source.from() or asynchronously pulling new elements out of a data structure with Source.unfoldAsync plus many more. Doing it this way ensures that the requests are only emitted when the time is right according to the downstream capacity or rate throttling.
It does not appear to me that you're actually using the throttler:
val throttler: ActorRef =
Source.actorRef(bufferSize = 1000, OverflowStrategy.dropNew)
.throttle(3, 1.second)
.to(Sink.actorRef(printer, NotUsed))
.run()
But I don't see any messages being sent to throttler in your code: throttler will only throttle messages sent to throttler.

No endpoint could be found for: test, please check your classpath contains the needed Camel component jar

I am trying to send and receive messages using akka-camel and created a sample example for producer and consumer like below :
Producer:
import akka.actor.{Actor, ActorSystem, Props}
import akka.camel.Producer
class CamelJmsProducer extends Actor with Producer {
override def endpointUri = "test"
}
object CamelJmsProducerApp extends App {
val system = ActorSystem("some-system")
val ref = system.actorOf(Props[CamelJmsProducer])
ref ! "HEY"
}
Consumer:
import akka.actor.{Actor, ActorSystem, Props}
import akka.camel.{CamelMessage, Consumer}
class CamelJmsConsumer extends Actor with Consumer {
override def receive = {
case msg: CamelMessage ⇒ println("RECEIVED >>> " + msg)
case _ ⇒ println("RECEIVED NOTHING>>> ")
}
override def endpointUri = "test"
}
object CamelJmsConsumerApp extends App {
val system = ActorSystem("some-system1")
system.actorOf(Props[CamelJmsConsumer])
}
But I am facing issue in both producer and consumer like below. What I am missing?
Producer:
java.lang.IllegalArgumentException: destination must be specified
Consumer :
Caused by: org.apache.camel.NoSuchEndpointException: No endpoint could
be found for: test, please check your classpath contains the needed
Camel component jar.
I believe you need to provide a name to the test mock endpoint, just test might not work. Can you try doing doing test:myMockEndpoint?
You can take a look here: http://camel.apache.org/components.html

Apache Bahir, send stuff to ActorReceiver

I am trying to setup a simple process with Spark Streaming, using Apache Bahir to connect to Akka. I tried to follow their example together with this older one. I have a simple forwarder actor
class ForwarderActor extends ActorReceiver {
def receive = {
case data: MyData => store(data)
}
}
and I create a stream with
val stream = AkkaUtils.createStream[RSVP](ssc, Props[ForwarderActor], actorName)
the configuration looks like this:
akka {
actor {
provider = "akka.remote.RemoteActorRefProvider"
}
remote {
enabled-transports = ["akka.remote.netty.tcp"]
netty.tcp {
hostname = "localhost"
port = 7777
}
}
}
and my problem is: how do I send messages to the Forwarder actor? Maybe I don't understand how Akka Remote is used in this case. When the app starts, I see a log
[akka.remote.Remoting] Remoting started; listening on addresses :[akka.tcp://test#localhost:7777]
and later on I see
[akka.remote.Remoting] Remoting now listens on addresses: [akka.tcp://streaming-actor-system-0#192.168.192.7:52369]
Which seems to remind to the description in the ScalaDoc:
/**
* A default ActorSystem creator. It will use a unique system name
* (streaming-actor-system-<spark-task-attempt-id>) to start an ActorSystem that supports remote
* communication.
*/
All in all I am not sure how I am supposed to send messages to the Forwarder actor. Thanks for any help!
Akka actors can send messages to other Akka actors running on a remote JVM. So... when the sender actor needs to know the address of the intended receiver actor.
AkkaUtil (Bahir) enables you to create a spark-stream from the messages that a ReceiverActor receives. But, where is is going to receive messages from ? Well... some remote actor. And to send messages this remote actor is going to need the address of your ReceiverActor which is running in your spark-application.
In general, you can not be too sure about the ip which will be running your spark application. So, we will make it so that the actor running with spark will tell the producer actor its reference and request it to send its things.
Just make sure that both applications are written using same version of Scala and are running the same JRE.
Now... lets first write the actor who will be the data source,
import akka.actor.{Actor, ActorRef, ActorLogging, ActorSystem, Props}
import akka.actor.Actor.Receive
import com.typesafe.config.{Config, ConfigFactory}
case class SendMeYourStringsRequest(requesterRef: ActorRef)
case class RequestedString(s: String)
class MyActor extends Actor with ActorLogging {
val theListOfMyStrings = List("one", "two", "three")
override def receive: Receive = {
case SendMeYourStringsRequest(requesterRef) => {
theListOfMyStrings.foreach(s => {
requesterRef ! RequestedString(s)
})
}
}
}
object MyApplication extends App {
val config = ConfigFactory.parseString(
"""
|akka{
| actor {
| provider = remote
| }
| remote {
| enabled-transports = ["akka.remote.netty.tcp"]
| untrusted-mode = off
| netty.tcp {
| hostname="my-ip-address"
| port=18000
| }
| }
|}
""".stripMargin
)
val actorSystem = ActorSystem("my-actor-system", config)
var myActor = actorSystem.actorOf(Props(classOf[MyActor]), "my-actor")
}
Now... lets write our simple spark app,
import akka.actor.{Actor, ActorRef, ActorLogging, ActorSystem, Props}
import akka.actor.Actor.Receive
import com.typesafe.config.{Config, ConfigFactory}
import org.apache.spark.SparkConf
import org.apache.spark.streaming.{Seconds, StreamingContext}
import org.apache.spark.streaming.akka.{ActorReceiver, AkkaUtils}
case class SendMeYourStringsRequest(requesterRef: ActorRef)
case class RequestedString(s: String)
class YourStringRequesterActor extends ActorReceiver {
def receive = {
case RequestedString(s) => store(s)
}
override def preStart(): Unit = {
val myActorPath = ActorPath.fromString("akka.tcp://my-actor-system#my-ip-address:18000/user/my-actor")
val myActorSelection = context.actorSelection(myActorPath)
myActorSelection ! SendMeYourStringsRequest(self)
}
}
object YourSparkApp {
def main(args: Array[String]): Unit = {
val sparkConf = new SparkConf().setAppName("ActorWordCount")
if (!sparkConf.contains("spark.master")) {
sparkConf.setMaster("local[2]")
}
val ssc = new StreamingContext(sparkConf, Seconds(2))
val stringStream = AkkaUtils.createStream[String](
ssc,
Props(classOf[YourStringRequesterActor]),
"your-string-requester-actor"
)
stringStream.foreach(println)
}
}
Note :: Just take care of my-ip-address. If there are any other problems, please let me know in comments.

Reaper pattern in Akka sends its Terminated message to dead letter box

I've been trying the reaper pattern suggested by the Akka team. It does sound good in theory, but the provided unit test does not pass in either sbt or Eclipse's JUnit runner.
import scala.concurrent.duration._
import akka.util.Timeout
import akka.actor._
import akka.testkit.{TestKit, ImplicitSender, TestProbe}
import org.scalatest.{WordSpec, BeforeAndAfterAll}
import org.scalatest.matchers.MustMatchers
import org.scalatest.junit.JUnitRunner
import org.junit.runner.RunWith
#RunWith(classOf[JUnitRunner])
class ReaperSpec extends TestKit(ActorSystem("ReaperSpec"))
with ImplicitSender with WordSpec with BeforeAndAfterAll with MustMatchers
{
import Reaper._
override def afterAll() {
system.shutdown()
}
// Our test reaper. Sends the snooper a message when all the souls have been reaped
private class TestReaper(snooper: ActorRef) extends Reaper {
def allSoulsReaped(): Unit = snooper ! "Dead"
}
"Reaper" should {
"work" in {
// Set up some dummy Actors
val a = TestProbe()
val b = TestProbe()
val c = TestProbe()
val d = TestProbe()
// Build our reaper
val reaper = system.actorOf(Props(new TestReaper(testActor)))
// Watch a couple
reaper ! WatchMe(a.ref)
reaper ! WatchMe(d.ref)
// Stop them
system.stop(a.ref)
system.stop(d.ref)
// Make sure we've been called
within (1 seconds, 5 seconds) {
expectMsg("Dead")
}
}
}
}
After adding logging to the actors, I noticed that the Terminated message ended up in the dead letter box. So the reaper actor never got the Terminated message.
[ERROR] [08/05/2014 11:10:27.244] [ReaperSpec-akka.actor.default-dispatcher-4]
[akka://ReaperSpec/user/$a] Monitored actor [Actor[akka://ReaperSpec/system/testActor2#-1060476766]] terminated (akka.actor.DeathPactException)
[INFO] [08/05/2014 11:10:27.248] [ReaperSpec-akka.actor.default-dispatcher-2]
[akka://ReaperSpec/user/$a] Message [akka.actor.Terminated] from Actor[akka://ReaperSpec/system/testActor5#2029818496] to Actor[akka://ReaperSpec/user/$a#1787786349] was not delivered.
[1] dead letters encountered, no more dead letters will be logged. This logging can be turned off or adjusted with configuration settings 'akka.log-dead-letters' and 'akka.log-dead-letters-during-shutdown'.
I thought that system.stop(watchedActorRef) would guarantee delivery of a Terminated message to all the actors that are watching the watchedActorRef. What am I missing?