Move content from akka-docs-new into akka-docs
Now the paradox documentation is no longer functional until we update akka-docs to generate from paradox instead of sphinx
This commit is contained in:
parent
f064d1321a
commit
5507147073
96 changed files with 0 additions and 35 deletions
32
akka-docs/src/test/scala/quickstart/HelloWorldApp.scala
Normal file
32
akka-docs/src/test/scala/quickstart/HelloWorldApp.scala
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
//#full-example
|
||||
package quickstart
|
||||
|
||||
import akka.actor.{ Actor, ActorRef, Props, ActorSystem }
|
||||
import scala.io.StdIn
|
||||
|
||||
object HelloWorldApp {
|
||||
def main(args: Array[String]): Unit = {
|
||||
//#create-send
|
||||
val system = ActorSystem("hello-world-actor-system")
|
||||
try {
|
||||
// Create hello world actor
|
||||
val helloWorldActor: ActorRef = system.actorOf(Props[HelloWorldActor], "HelloWorldActor")
|
||||
// Send message to actor
|
||||
helloWorldActor ! "World"
|
||||
// Exit the system after ENTER is pressed
|
||||
StdIn.readLine()
|
||||
} finally {
|
||||
system.terminate()
|
||||
}
|
||||
//#create-send
|
||||
}
|
||||
}
|
||||
|
||||
//#actor-impl
|
||||
class HelloWorldActor extends Actor {
|
||||
def receive = {
|
||||
case msg: String => println(s"Hello $msg")
|
||||
}
|
||||
}
|
||||
//#actor-impl
|
||||
//#full-example
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
package tutorial_1
|
||||
|
||||
import akka.actor.{ Actor, Props }
|
||||
import akka.testkit.AkkaSpec
|
||||
|
||||
//#print-refs
|
||||
class PrintMyActorRefActor extends Actor {
|
||||
override def receive: Receive = {
|
||||
case "printit" =>
|
||||
val secondRef = context.actorOf(Props.empty, "second-actor")
|
||||
println(s"Second: $secondRef")
|
||||
}
|
||||
}
|
||||
//#print-refs
|
||||
|
||||
//#start-stop
|
||||
class StartStopActor1 extends Actor {
|
||||
override def preStart(): Unit = {
|
||||
println("first started")
|
||||
context.actorOf(Props[StartStopActor2], "second")
|
||||
}
|
||||
override def postStop(): Unit = println("first stopped")
|
||||
|
||||
override def receive: Receive = {
|
||||
case "stop" => context.stop(self)
|
||||
}
|
||||
}
|
||||
|
||||
class StartStopActor2 extends Actor {
|
||||
override def preStart(): Unit = println("second started")
|
||||
override def postStop(): Unit = println("second stopped")
|
||||
|
||||
// Actor.emptyBehavior is a useful placeholder when we don't
|
||||
// want to handle any messages in the actor.
|
||||
override def receive: Receive = Actor.emptyBehavior
|
||||
}
|
||||
//#start-stop
|
||||
|
||||
//#supervise
|
||||
class SupervisingActor extends Actor {
|
||||
val child = context.actorOf(Props[SupervisedActor], "supervised-actor")
|
||||
|
||||
override def receive: Receive = {
|
||||
case "failChild" => child ! "fail"
|
||||
}
|
||||
}
|
||||
|
||||
class SupervisedActor extends Actor {
|
||||
override def preStart(): Unit = println("supervised actor started")
|
||||
override def postStop(): Unit = println("supervised actor stopped")
|
||||
|
||||
override def receive: Receive = {
|
||||
case "fail" =>
|
||||
println("supervised actor fails now")
|
||||
throw new Exception("I failed!")
|
||||
}
|
||||
}
|
||||
//#supervise
|
||||
|
||||
class ActorHierarchyExperiments extends AkkaSpec {
|
||||
"create top and child actor" in {
|
||||
// format: OFF
|
||||
//#print-refs
|
||||
|
||||
val firstRef = system.actorOf(Props[PrintMyActorRefActor], "first-actor")
|
||||
println(s"First : $firstRef")
|
||||
firstRef ! "printit"
|
||||
//#print-refs
|
||||
// format: ON
|
||||
}
|
||||
|
||||
"start and stop actors" in {
|
||||
// format: OFF
|
||||
//#start-stop
|
||||
|
||||
val first = system.actorOf(Props[StartStopActor1], "first")
|
||||
first ! "stop"
|
||||
//#start-stop
|
||||
// format: ON
|
||||
}
|
||||
|
||||
"supervise actors" in {
|
||||
// format: OFF
|
||||
//#supervise
|
||||
|
||||
val supervisingActor = system.actorOf(Props[SupervisingActor], "supervising-actor")
|
||||
supervisingActor ! "failChild"
|
||||
//#supervise
|
||||
// format: ON
|
||||
}
|
||||
}
|
||||
26
akka-docs/src/test/scala/tutorial_1/IotApp.scala
Normal file
26
akka-docs/src/test/scala/tutorial_1/IotApp.scala
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_1
|
||||
|
||||
//#iot-app
|
||||
import akka.actor.ActorSystem
|
||||
import scala.io.StdIn
|
||||
|
||||
object IotApp {
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
val system = ActorSystem("iot-system")
|
||||
|
||||
try {
|
||||
// Create top level supervisor
|
||||
val supervisor = system.actorOf(IotSupervisor.props(), "iot-supervisor")
|
||||
// Exit the system after ENTER is pressed
|
||||
StdIn.readLine()
|
||||
} finally {
|
||||
system.terminate()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//#iot-app
|
||||
21
akka-docs/src/test/scala/tutorial_1/IotSupervisor.scala
Normal file
21
akka-docs/src/test/scala/tutorial_1/IotSupervisor.scala
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_1
|
||||
|
||||
//#iot-supervisor
|
||||
import akka.actor.{ Actor, ActorLogging, Props }
|
||||
|
||||
object IotSupervisor {
|
||||
def props(): Props = Props(new IotSupervisor)
|
||||
}
|
||||
|
||||
class IotSupervisor extends Actor with ActorLogging {
|
||||
override def preStart(): Unit = log.info("IoT Application started")
|
||||
override def postStop(): Unit = log.info("IoT Application stopped")
|
||||
|
||||
// No need to handle any messages
|
||||
override def receive = Actor.emptyBehavior
|
||||
|
||||
}
|
||||
//#iot-supervisor
|
||||
36
akka-docs/src/test/scala/tutorial_2/Device.scala
Normal file
36
akka-docs/src/test/scala/tutorial_2/Device.scala
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_2
|
||||
|
||||
//#full-device
|
||||
import akka.actor.{ Actor, ActorLogging, Props }
|
||||
|
||||
object Device {
|
||||
def props(groupId: String, deviceId: String): Props = Props(new Device(groupId, deviceId))
|
||||
|
||||
final case class RecordTemperature(requestId: Long, value: Double)
|
||||
final case class TemperatureRecorded(requestId: Long)
|
||||
|
||||
final case class ReadTemperature(requestId: Long)
|
||||
final case class RespondTemperature(requestId: Long, value: Option[Double])
|
||||
}
|
||||
|
||||
class Device(groupId: String, deviceId: String) extends Actor with ActorLogging {
|
||||
import Device._
|
||||
var lastTemperatureReading: Option[Double] = None
|
||||
|
||||
override def preStart(): Unit = log.info("Device actor {}-{} started", groupId, deviceId)
|
||||
override def postStop(): Unit = log.info("Device actor {}-{} stopped", groupId, deviceId)
|
||||
|
||||
override def receive: Receive = {
|
||||
case RecordTemperature(id, value) =>
|
||||
log.info("Recorded temperature reading {} with {}", value, id)
|
||||
lastTemperatureReading = Some(value)
|
||||
sender() ! TemperatureRecorded(id)
|
||||
|
||||
case ReadTemperature(id) =>
|
||||
sender() ! RespondTemperature(id, lastTemperatureReading)
|
||||
}
|
||||
}
|
||||
//#full-device
|
||||
54
akka-docs/src/test/scala/tutorial_2/DeviceInProgress.scala
Normal file
54
akka-docs/src/test/scala/tutorial_2/DeviceInProgress.scala
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
package tutorial_2
|
||||
|
||||
import tutorial_5.Device.{ ReadTemperature, RecordTemperature, RespondTemperature, TemperatureRecorded }
|
||||
|
||||
object DeviceInProgress1 {
|
||||
|
||||
object Device {
|
||||
//#read-protocol-1
|
||||
final case object ReadTemperature
|
||||
final case class RespondTemperature(value: Option[Double])
|
||||
//#read-protocol-1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object DeviceInProgress2 {
|
||||
|
||||
//#device-with-read
|
||||
import akka.actor.{ Actor, ActorLogging, Props }
|
||||
|
||||
object Device {
|
||||
def props(groupId: String, deviceId: String): Props = Props(new Device(groupId, deviceId))
|
||||
|
||||
//#read-protocol-2
|
||||
final case class ReadTemperature(requestId: Long)
|
||||
final case class RespondTemperature(requestId: Long, value: Option[Double])
|
||||
//#read-protocol-2
|
||||
}
|
||||
|
||||
class Device(groupId: String, deviceId: String) extends Actor with ActorLogging {
|
||||
var lastTemperatureReading: Option[Double] = None
|
||||
|
||||
override def preStart(): Unit = log.info("Device actor {}-{} started", groupId, deviceId)
|
||||
override def postStop(): Unit = log.info("Device actor {}-{} stopped", groupId, deviceId)
|
||||
|
||||
override def receive: Receive = {
|
||||
case ReadTemperature(id) =>
|
||||
sender() ! RespondTemperature(id, lastTemperatureReading)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//#device-with-read
|
||||
|
||||
}
|
||||
|
||||
object DeviceInProgress3 {
|
||||
|
||||
object Device {
|
||||
//#write-protocol-1
|
||||
final case class RecordTemperature(value: Double)
|
||||
//#write-protocol-1
|
||||
}
|
||||
}
|
||||
51
akka-docs/src/test/scala/tutorial_2/DeviceSpec.scala
Normal file
51
akka-docs/src/test/scala/tutorial_2/DeviceSpec.scala
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_2
|
||||
|
||||
import akka.testkit.{ AkkaSpec, TestProbe }
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class DeviceSpec extends AkkaSpec {
|
||||
|
||||
"Device actor" must {
|
||||
|
||||
//#device-read-test
|
||||
"reply with empty reading if no temperature is known" in {
|
||||
val probe = TestProbe()
|
||||
val deviceActor = system.actorOf(Device.props("group", "device"))
|
||||
|
||||
deviceActor.tell(Device.ReadTemperature(requestId = 42), probe.ref)
|
||||
val response = probe.expectMsgType[Device.RespondTemperature]
|
||||
response.requestId should ===(42)
|
||||
response.value should ===(None)
|
||||
}
|
||||
//#device-read-test
|
||||
|
||||
//#device-write-read-test
|
||||
"reply with latest temperature reading" in {
|
||||
val probe = TestProbe()
|
||||
val deviceActor = system.actorOf(Device.props("group", "device"))
|
||||
|
||||
deviceActor.tell(Device.RecordTemperature(requestId = 1, 24.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 1))
|
||||
|
||||
deviceActor.tell(Device.ReadTemperature(requestId = 2), probe.ref)
|
||||
val response1 = probe.expectMsgType[Device.RespondTemperature]
|
||||
response1.requestId should ===(2)
|
||||
response1.value should ===(Some(24.0))
|
||||
|
||||
deviceActor.tell(Device.RecordTemperature(requestId = 3, 55.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 3))
|
||||
|
||||
deviceActor.tell(Device.ReadTemperature(requestId = 4), probe.ref)
|
||||
val response2 = probe.expectMsgType[Device.RespondTemperature]
|
||||
response2.requestId should ===(4)
|
||||
response2.value should ===(Some(55.0))
|
||||
}
|
||||
//#device-write-read-test
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
47
akka-docs/src/test/scala/tutorial_3/Device.scala
Normal file
47
akka-docs/src/test/scala/tutorial_3/Device.scala
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_3
|
||||
|
||||
import akka.actor.{ Actor, ActorLogging, Props }
|
||||
import tutorial_3.Device.{ ReadTemperature, RecordTemperature, RespondTemperature, TemperatureRecorded }
|
||||
import tutorial_3.DeviceManager.{ DeviceRegistered, RequestTrackDevice }
|
||||
|
||||
//#device-with-register
|
||||
object Device {
|
||||
def props(groupId: String, deviceId: String): Props = Props(new Device(groupId, deviceId))
|
||||
|
||||
final case class RecordTemperature(requestId: Long, value: Double)
|
||||
final case class TemperatureRecorded(requestId: Long)
|
||||
|
||||
final case class ReadTemperature(requestId: Long)
|
||||
final case class RespondTemperature(requestId: Long, value: Option[Double])
|
||||
}
|
||||
|
||||
class Device(groupId: String, deviceId: String) extends Actor with ActorLogging {
|
||||
var lastTemperatureReading: Option[Double] = None
|
||||
|
||||
override def preStart(): Unit = log.info("Device actor {}-{} started", groupId, deviceId)
|
||||
|
||||
override def postStop(): Unit = log.info("Device actor {}-{} stopped", groupId, deviceId)
|
||||
|
||||
override def receive: Receive = {
|
||||
case RequestTrackDevice(`groupId`, `deviceId`) =>
|
||||
sender() ! DeviceRegistered
|
||||
|
||||
case RequestTrackDevice(groupId, deviceId) =>
|
||||
log.warning(
|
||||
"Ignoring TrackDevice request for {}-{}.This actor is responsible for {}-{}.",
|
||||
groupId, deviceId, this.groupId, this.deviceId
|
||||
)
|
||||
|
||||
case RecordTemperature(id, value) =>
|
||||
log.info("Recorded temperature reading {} with {}", value, id)
|
||||
lastTemperatureReading = Some(value)
|
||||
sender() ! TemperatureRecorded(id)
|
||||
|
||||
case ReadTemperature(id) =>
|
||||
sender() ! RespondTemperature(id, lastTemperatureReading)
|
||||
}
|
||||
}
|
||||
//#device-with-register
|
||||
75
akka-docs/src/test/scala/tutorial_3/DeviceGroup.scala
Normal file
75
akka-docs/src/test/scala/tutorial_3/DeviceGroup.scala
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_3
|
||||
|
||||
import akka.actor.{ Actor, ActorLogging, ActorRef, Props, Terminated }
|
||||
import tutorial_3.DeviceGroup._
|
||||
import tutorial_3.DeviceManager.RequestTrackDevice
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
//#device-group-full
|
||||
//#device-group-register
|
||||
object DeviceGroup {
|
||||
def props(groupId: String): Props = Props(new DeviceGroup(groupId))
|
||||
//#device-group-register
|
||||
|
||||
final case class RequestDeviceList(requestId: Long)
|
||||
final case class ReplyDeviceList(requestId: Long, ids: Set[String])
|
||||
//#device-group-register
|
||||
}
|
||||
//#device-group-register
|
||||
//#device-group-register
|
||||
//#device-group-remove
|
||||
|
||||
class DeviceGroup(groupId: String) extends Actor with ActorLogging {
|
||||
var deviceIdToActor = Map.empty[String, ActorRef]
|
||||
//#device-group-register
|
||||
var actorToDeviceId = Map.empty[ActorRef, String]
|
||||
//#device-group-register
|
||||
|
||||
override def preStart(): Unit = log.info("DeviceGroup {} started", groupId)
|
||||
|
||||
override def postStop(): Unit = log.info("DeviceGroup {} stopped", groupId)
|
||||
|
||||
override def receive: Receive = {
|
||||
case trackMsg @ RequestTrackDevice(`groupId`, _) =>
|
||||
deviceIdToActor.get(trackMsg.deviceId) match {
|
||||
case Some(deviceActor) =>
|
||||
deviceActor forward trackMsg
|
||||
case None =>
|
||||
log.info("Creating device actor for {}", trackMsg.deviceId)
|
||||
val deviceActor = context.actorOf(Device.props(groupId, trackMsg.deviceId), s"device-${trackMsg.deviceId}")
|
||||
//#device-group-register
|
||||
context.watch(deviceActor)
|
||||
actorToDeviceId += deviceActor -> trackMsg.deviceId
|
||||
//#device-group-register
|
||||
deviceIdToActor += trackMsg.deviceId -> deviceActor
|
||||
deviceActor forward trackMsg
|
||||
}
|
||||
|
||||
case RequestTrackDevice(groupId, deviceId) =>
|
||||
log.warning(
|
||||
"Ignoring TrackDevice request for {}. This actor is responsible for {}.",
|
||||
groupId, this.groupId
|
||||
)
|
||||
//#device-group-register
|
||||
//#device-group-remove
|
||||
|
||||
case RequestDeviceList(requestId) =>
|
||||
sender() ! ReplyDeviceList(requestId, deviceIdToActor.keySet)
|
||||
//#device-group-remove
|
||||
|
||||
case Terminated(deviceActor) =>
|
||||
val deviceId = actorToDeviceId(deviceActor)
|
||||
log.info("Device actor for {} has been terminated", deviceId)
|
||||
actorToDeviceId -= deviceActor
|
||||
deviceIdToActor -= deviceId
|
||||
|
||||
//#device-group-register
|
||||
}
|
||||
}
|
||||
//#device-group-remove
|
||||
//#device-group-register
|
||||
//#device-group-full
|
||||
103
akka-docs/src/test/scala/tutorial_3/DeviceGroupSpec.scala
Normal file
103
akka-docs/src/test/scala/tutorial_3/DeviceGroupSpec.scala
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package tutorial_3
|
||||
|
||||
import akka.actor.PoisonPill
|
||||
import akka.testkit.{ AkkaSpec, TestProbe }
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class DeviceGroupSpec extends AkkaSpec {
|
||||
|
||||
"DeviceGroup actor" must {
|
||||
|
||||
//#device-group-test-registration
|
||||
"be able to register a device actor" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor1 = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device2"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor2 = probe.lastSender
|
||||
deviceActor1 should !==(deviceActor2)
|
||||
|
||||
// Check that the device actors are working
|
||||
deviceActor1.tell(Device.RecordTemperature(requestId = 0, 1.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 0))
|
||||
deviceActor2.tell(Device.RecordTemperature(requestId = 1, 2.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 1))
|
||||
}
|
||||
|
||||
"ignore requests for wrong groupId" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("wrongGroup", "device1"), probe.ref)
|
||||
probe.expectNoMsg(500.milliseconds)
|
||||
}
|
||||
//#device-group-test-registration
|
||||
|
||||
//#device-group-test3
|
||||
"return same actor for same deviceId" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor1 = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor2 = probe.lastSender
|
||||
|
||||
deviceActor1 should ===(deviceActor2)
|
||||
}
|
||||
//#device-group-test3
|
||||
|
||||
//#device-group-list-terminate-test
|
||||
"be able to list active devices" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device2"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
|
||||
groupActor.tell(DeviceGroup.RequestDeviceList(requestId = 0), probe.ref)
|
||||
probe.expectMsg(DeviceGroup.ReplyDeviceList(requestId = 0, Set("device1", "device2")))
|
||||
}
|
||||
|
||||
"be able to list active devices after one shuts down" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val toShutDown = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device2"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
|
||||
groupActor.tell(DeviceGroup.RequestDeviceList(requestId = 0), probe.ref)
|
||||
probe.expectMsg(DeviceGroup.ReplyDeviceList(requestId = 0, Set("device1", "device2")))
|
||||
|
||||
probe.watch(toShutDown)
|
||||
toShutDown ! PoisonPill
|
||||
probe.expectTerminated(toShutDown)
|
||||
|
||||
groupActor.tell(DeviceGroup.RequestDeviceList(requestId = 1), probe.ref)
|
||||
probe.expectMsg(DeviceGroup.ReplyDeviceList(requestId = 1, Set("device2")))
|
||||
}
|
||||
//#device-group-list-terminate-test
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
51
akka-docs/src/test/scala/tutorial_3/DeviceManager.scala
Normal file
51
akka-docs/src/test/scala/tutorial_3/DeviceManager.scala
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package tutorial_3
|
||||
|
||||
import akka.actor.{ Actor, ActorLogging, ActorRef, Props, Terminated }
|
||||
import tutorial_3.DeviceManager.RequestTrackDevice
|
||||
|
||||
//#device-manager-full
|
||||
object DeviceManager {
|
||||
def props(): Props = Props(new DeviceManager)
|
||||
|
||||
//#device-manager-msgs
|
||||
final case class RequestTrackDevice(groupId: String, deviceId: String)
|
||||
case object DeviceRegistered
|
||||
//#device-manager-msgs
|
||||
}
|
||||
|
||||
class DeviceManager extends Actor with ActorLogging {
|
||||
var groupIdToActor = Map.empty[String, ActorRef]
|
||||
var actorToGroupId = Map.empty[ActorRef, String]
|
||||
|
||||
override def preStart(): Unit = log.info("DeviceManager started")
|
||||
|
||||
override def postStop(): Unit = log.info("DeviceManager stopped")
|
||||
|
||||
override def receive = {
|
||||
case trackMsg @ RequestTrackDevice(groupId, _) =>
|
||||
groupIdToActor.get(groupId) match {
|
||||
case Some(ref) =>
|
||||
ref forward trackMsg
|
||||
case None =>
|
||||
log.info("Creating device group actor for {}", groupId)
|
||||
val groupActor = context.actorOf(DeviceGroup.props(groupId), "group-" + groupId)
|
||||
context.watch(groupActor)
|
||||
groupActor forward trackMsg
|
||||
groupIdToActor += groupId -> groupActor
|
||||
actorToGroupId += groupActor -> groupId
|
||||
}
|
||||
|
||||
case Terminated(groupActor) =>
|
||||
val groupId = actorToGroupId(groupActor)
|
||||
log.info("Device group actor for {} has been terminated", groupId)
|
||||
actorToGroupId -= groupActor
|
||||
groupIdToActor -= groupId
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
//#device-manager-full
|
||||
69
akka-docs/src/test/scala/tutorial_3/DeviceSpec.scala
Normal file
69
akka-docs/src/test/scala/tutorial_3/DeviceSpec.scala
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_3
|
||||
|
||||
import akka.testkit.{ AkkaSpec, TestProbe }
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class DeviceSpec extends AkkaSpec {
|
||||
|
||||
"Device actor" must {
|
||||
|
||||
//#device-registration-tests
|
||||
"reply to registration requests" in {
|
||||
val probe = TestProbe()
|
||||
val deviceActor = system.actorOf(Device.props("group", "device"))
|
||||
|
||||
deviceActor.tell(DeviceManager.RequestTrackDevice("group", "device"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
probe.lastSender should ===(deviceActor)
|
||||
}
|
||||
|
||||
"ignore wrong registration requests" in {
|
||||
val probe = TestProbe()
|
||||
val deviceActor = system.actorOf(Device.props("group", "device"))
|
||||
|
||||
deviceActor.tell(DeviceManager.RequestTrackDevice("wrongGroup", "device"), probe.ref)
|
||||
probe.expectNoMsg(500.milliseconds)
|
||||
|
||||
deviceActor.tell(DeviceManager.RequestTrackDevice("group", "Wrongdevice"), probe.ref)
|
||||
probe.expectNoMsg(500.milliseconds)
|
||||
}
|
||||
//#device-registration-tests
|
||||
|
||||
"reply with empty reading if no temperature is known" in {
|
||||
val probe = TestProbe()
|
||||
val deviceActor = system.actorOf(Device.props("group", "device"))
|
||||
|
||||
deviceActor.tell(Device.ReadTemperature(requestId = 42), probe.ref)
|
||||
val response = probe.expectMsgType[Device.RespondTemperature]
|
||||
response.requestId should ===(42)
|
||||
response.value should ===(None)
|
||||
}
|
||||
|
||||
"reply with latest temperature reading" in {
|
||||
val probe = TestProbe()
|
||||
val deviceActor = system.actorOf(Device.props("group", "device"))
|
||||
|
||||
deviceActor.tell(Device.RecordTemperature(requestId = 1, 24.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 1))
|
||||
|
||||
deviceActor.tell(Device.ReadTemperature(requestId = 2), probe.ref)
|
||||
val response1 = probe.expectMsgType[Device.RespondTemperature]
|
||||
response1.requestId should ===(2)
|
||||
response1.value should ===(Some(24.0))
|
||||
|
||||
deviceActor.tell(Device.RecordTemperature(requestId = 3, 55.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 3))
|
||||
|
||||
deviceActor.tell(Device.ReadTemperature(requestId = 4), probe.ref)
|
||||
val response2 = probe.expectMsgType[Device.RespondTemperature]
|
||||
response2.requestId should ===(4)
|
||||
response2.value should ===(Some(55.0))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
46
akka-docs/src/test/scala/tutorial_4/Device.scala
Normal file
46
akka-docs/src/test/scala/tutorial_4/Device.scala
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_4
|
||||
|
||||
import akka.actor.{ Actor, ActorLogging, Props }
|
||||
import tutorial_4.Device.{ ReadTemperature, RecordTemperature, RespondTemperature, TemperatureRecorded }
|
||||
import tutorial_4.DeviceManager.{ DeviceRegistered, RequestTrackDevice }
|
||||
|
||||
object Device {
|
||||
|
||||
def props(groupId: String, deviceId: String): Props = Props(new Device(groupId, deviceId))
|
||||
|
||||
final case class RecordTemperature(requestId: Long, value: Double)
|
||||
final case class TemperatureRecorded(requestId: Long)
|
||||
|
||||
final case class ReadTemperature(requestId: Long)
|
||||
final case class RespondTemperature(requestId: Long, value: Option[Double])
|
||||
}
|
||||
|
||||
class Device(groupId: String, deviceId: String) extends Actor with ActorLogging {
|
||||
var lastTemperatureReading: Option[Double] = None
|
||||
|
||||
override def preStart(): Unit = log.info("Device actor {}-{} started", groupId, deviceId)
|
||||
|
||||
override def postStop(): Unit = log.info("Device actor {}-{} stopped", groupId, deviceId)
|
||||
|
||||
override def receive: Receive = {
|
||||
case RequestTrackDevice(`groupId`, `deviceId`) =>
|
||||
sender() ! DeviceRegistered
|
||||
|
||||
case RequestTrackDevice(groupId, deviceId) =>
|
||||
log.warning(
|
||||
"Ignoring TrackDevice request for {}-{}.This actor is responsible for {}-{}.",
|
||||
groupId, deviceId, this.groupId, this.deviceId
|
||||
)
|
||||
|
||||
case RecordTemperature(id, value) =>
|
||||
log.info("Recorded temperature reading {} with {}", value, id)
|
||||
lastTemperatureReading = Some(value)
|
||||
sender() ! TemperatureRecorded(id)
|
||||
|
||||
case ReadTemperature(id) =>
|
||||
sender() ! RespondTemperature(id, lastTemperatureReading)
|
||||
}
|
||||
}
|
||||
83
akka-docs/src/test/scala/tutorial_4/DeviceGroup.scala
Normal file
83
akka-docs/src/test/scala/tutorial_4/DeviceGroup.scala
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_4
|
||||
|
||||
import akka.actor.{ Actor, ActorLogging, ActorRef, Props, Terminated }
|
||||
import tutorial_4.DeviceGroup._
|
||||
import tutorial_4.DeviceManager.RequestTrackDevice
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
object DeviceGroup {
|
||||
def props(groupId: String): Props = Props(new DeviceGroup(groupId))
|
||||
|
||||
final case class RequestDeviceList(requestId: Long)
|
||||
final case class ReplyDeviceList(requestId: Long, ids: Set[String])
|
||||
|
||||
//#query-protocol
|
||||
final case class RequestAllTemperatures(requestId: Long)
|
||||
final case class RespondAllTemperatures(requestId: Long, temperatures: Map[String, TemperatureReading])
|
||||
|
||||
sealed trait TemperatureReading
|
||||
final case class Temperature(value: Double) extends TemperatureReading
|
||||
case object TemperatureNotAvailable extends TemperatureReading
|
||||
case object DeviceNotAvailable extends TemperatureReading
|
||||
case object DeviceTimedOut extends TemperatureReading
|
||||
//#query-protocol
|
||||
}
|
||||
|
||||
//#query-added
|
||||
class DeviceGroup(groupId: String) extends Actor with ActorLogging {
|
||||
var deviceIdToActor = Map.empty[String, ActorRef]
|
||||
var actorToDeviceId = Map.empty[ActorRef, String]
|
||||
var nextCollectionId = 0L
|
||||
|
||||
override def preStart(): Unit = log.info("DeviceGroup {} started", groupId)
|
||||
|
||||
override def postStop(): Unit = log.info("DeviceGroup {} stopped", groupId)
|
||||
|
||||
override def receive: Receive = {
|
||||
//#query-added
|
||||
case trackMsg @ RequestTrackDevice(`groupId`, _) =>
|
||||
deviceIdToActor.get(trackMsg.deviceId) match {
|
||||
case Some(ref) =>
|
||||
ref forward trackMsg
|
||||
case None =>
|
||||
log.info("Creating device actor for {}", trackMsg.deviceId)
|
||||
val deviceActor = context.actorOf(Device.props(groupId, trackMsg.deviceId), "device-" + trackMsg.deviceId)
|
||||
context.watch(deviceActor)
|
||||
deviceActor forward trackMsg
|
||||
deviceIdToActor += trackMsg.deviceId -> deviceActor
|
||||
actorToDeviceId += deviceActor -> trackMsg.deviceId
|
||||
}
|
||||
|
||||
case RequestTrackDevice(groupId, deviceId) =>
|
||||
log.warning(
|
||||
"Ignoring TrackDevice request for {}. This actor is responsible for {}.",
|
||||
groupId, this.groupId
|
||||
)
|
||||
|
||||
case RequestDeviceList(requestId) =>
|
||||
sender() ! ReplyDeviceList(requestId, deviceIdToActor.keySet)
|
||||
|
||||
case Terminated(deviceActor) =>
|
||||
val deviceId = actorToDeviceId(deviceActor)
|
||||
log.info("Device actor for {} has been terminated", deviceId)
|
||||
actorToDeviceId -= deviceActor
|
||||
deviceIdToActor -= deviceId
|
||||
|
||||
//#query-added
|
||||
// ... other cases omitted
|
||||
|
||||
case RequestAllTemperatures(requestId) =>
|
||||
context.actorOf(DeviceGroupQuery.props(
|
||||
actorToDeviceId = actorToDeviceId,
|
||||
requestId = requestId,
|
||||
requester = sender(),
|
||||
3.seconds
|
||||
))
|
||||
}
|
||||
|
||||
}
|
||||
//#query-added
|
||||
104
akka-docs/src/test/scala/tutorial_4/DeviceGroupQuery.scala
Normal file
104
akka-docs/src/test/scala/tutorial_4/DeviceGroupQuery.scala
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_4
|
||||
|
||||
import akka.actor.{ Actor, ActorLogging, ActorRef, Props, Terminated }
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
//#query-full
|
||||
//#query-outline
|
||||
object DeviceGroupQuery {
|
||||
case object CollectionTimeout
|
||||
|
||||
def props(
|
||||
actorToDeviceId: Map[ActorRef, String],
|
||||
requestId: Long,
|
||||
requester: ActorRef,
|
||||
timeout: FiniteDuration
|
||||
): Props = {
|
||||
Props(new DeviceGroupQuery(actorToDeviceId, requestId, requester, timeout))
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceGroupQuery(
|
||||
actorToDeviceId: Map[ActorRef, String],
|
||||
requestId: Long,
|
||||
requester: ActorRef,
|
||||
timeout: FiniteDuration
|
||||
) extends Actor with ActorLogging {
|
||||
import DeviceGroupQuery._
|
||||
import context.dispatcher
|
||||
val queryTimeoutTimer = context.system.scheduler.scheduleOnce(timeout, self, CollectionTimeout)
|
||||
|
||||
override def preStart(): Unit = {
|
||||
actorToDeviceId.keysIterator.foreach { deviceActor =>
|
||||
context.watch(deviceActor)
|
||||
deviceActor ! Device.ReadTemperature(0)
|
||||
}
|
||||
}
|
||||
|
||||
override def postStop(): Unit = {
|
||||
queryTimeoutTimer.cancel()
|
||||
}
|
||||
|
||||
//#query-outline
|
||||
//#query-state
|
||||
override def receive: Receive =
|
||||
waitingForReplies(
|
||||
Map.empty,
|
||||
actorToDeviceId.keySet
|
||||
)
|
||||
|
||||
def waitingForReplies(
|
||||
repliesSoFar: Map[String, DeviceGroup.TemperatureReading],
|
||||
stillWaiting: Set[ActorRef]
|
||||
): Receive = {
|
||||
case Device.RespondTemperature(0, valueOption) =>
|
||||
val deviceActor = sender()
|
||||
val reading = valueOption match {
|
||||
case Some(value) => DeviceGroup.Temperature(value)
|
||||
case None => DeviceGroup.TemperatureNotAvailable
|
||||
}
|
||||
receivedResponse(deviceActor, reading, stillWaiting, repliesSoFar)
|
||||
|
||||
case Terminated(deviceActor) =>
|
||||
receivedResponse(deviceActor, DeviceGroup.DeviceNotAvailable, stillWaiting, repliesSoFar)
|
||||
|
||||
case CollectionTimeout =>
|
||||
val timedOutReplies =
|
||||
stillWaiting.map { deviceActor =>
|
||||
val deviceId = actorToDeviceId(deviceActor)
|
||||
deviceId -> DeviceGroup.DeviceTimedOut
|
||||
}
|
||||
requester ! DeviceGroup.RespondAllTemperatures(requestId, repliesSoFar ++ timedOutReplies)
|
||||
context.stop(self)
|
||||
}
|
||||
//#query-state
|
||||
|
||||
//#query-collect-reply
|
||||
def receivedResponse(
|
||||
deviceActor: ActorRef,
|
||||
reading: DeviceGroup.TemperatureReading,
|
||||
stillWaiting: Set[ActorRef],
|
||||
repliesSoFar: Map[String, DeviceGroup.TemperatureReading]
|
||||
): Unit = {
|
||||
context.unwatch(deviceActor)
|
||||
val deviceId = actorToDeviceId(deviceActor)
|
||||
val newStillWaiting = stillWaiting - deviceActor
|
||||
|
||||
val newRepliesSoFar = repliesSoFar + (deviceId -> reading)
|
||||
if (newStillWaiting.isEmpty) {
|
||||
requester ! DeviceGroup.RespondAllTemperatures(requestId, newRepliesSoFar)
|
||||
context.stop(self)
|
||||
} else {
|
||||
context.become(waitingForReplies(newRepliesSoFar, newStillWaiting))
|
||||
}
|
||||
}
|
||||
//#query-collect-reply
|
||||
|
||||
//#query-outline
|
||||
}
|
||||
//#query-outline
|
||||
//#query-full
|
||||
167
akka-docs/src/test/scala/tutorial_4/DeviceGroupQuerySpec.scala
Normal file
167
akka-docs/src/test/scala/tutorial_4/DeviceGroupQuerySpec.scala
Normal file
|
|
@ -0,0 +1,167 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_4
|
||||
|
||||
import akka.actor.PoisonPill
|
||||
import akka.testkit.{ AkkaSpec, TestProbe }
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class DeviceGroupQuerySpec extends AkkaSpec {
|
||||
|
||||
"DeviceGroupQuery" must {
|
||||
|
||||
//#query-test-normal
|
||||
"return temperature value for working devices" in {
|
||||
val requester = TestProbe()
|
||||
|
||||
val device1 = TestProbe()
|
||||
val device2 = TestProbe()
|
||||
|
||||
val queryActor = system.actorOf(DeviceGroupQuery.props(
|
||||
actorToDeviceId = Map(device1.ref -> "device1", device2.ref -> "device2"),
|
||||
requestId = 1,
|
||||
requester = requester.ref,
|
||||
timeout = 3.seconds
|
||||
))
|
||||
|
||||
device1.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
device2.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(1.0)), device1.ref)
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(2.0)), device2.ref)
|
||||
|
||||
requester.expectMsg(DeviceGroup.RespondAllTemperatures(
|
||||
requestId = 1,
|
||||
temperatures = Map(
|
||||
"device1" -> DeviceGroup.Temperature(1.0),
|
||||
"device2" -> DeviceGroup.Temperature(2.0)
|
||||
)
|
||||
))
|
||||
}
|
||||
//#query-test-normal
|
||||
|
||||
//#query-test-no-reading
|
||||
"return TemperatureNotAvailable for devices with no readings" in {
|
||||
val requester = TestProbe()
|
||||
|
||||
val device1 = TestProbe()
|
||||
val device2 = TestProbe()
|
||||
|
||||
val queryActor = system.actorOf(DeviceGroupQuery.props(
|
||||
actorToDeviceId = Map(device1.ref -> "device1", device2.ref -> "device2"),
|
||||
requestId = 1,
|
||||
requester = requester.ref,
|
||||
timeout = 3.seconds
|
||||
))
|
||||
|
||||
device1.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
device2.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, None), device1.ref)
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(2.0)), device2.ref)
|
||||
|
||||
requester.expectMsg(DeviceGroup.RespondAllTemperatures(
|
||||
requestId = 1,
|
||||
temperatures = Map(
|
||||
"device1" -> DeviceGroup.TemperatureNotAvailable,
|
||||
"device2" -> DeviceGroup.Temperature(2.0)
|
||||
)
|
||||
))
|
||||
}
|
||||
//#query-test-no-reading
|
||||
|
||||
//#query-test-stopped
|
||||
"return DeviceNotAvailable if device stops before answering" in {
|
||||
val requester = TestProbe()
|
||||
|
||||
val device1 = TestProbe()
|
||||
val device2 = TestProbe()
|
||||
|
||||
val queryActor = system.actorOf(DeviceGroupQuery.props(
|
||||
actorToDeviceId = Map(device1.ref -> "device1", device2.ref -> "device2"),
|
||||
requestId = 1,
|
||||
requester = requester.ref,
|
||||
timeout = 3.seconds
|
||||
))
|
||||
|
||||
device1.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
device2.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(1.0)), device1.ref)
|
||||
device2.ref ! PoisonPill
|
||||
|
||||
requester.expectMsg(DeviceGroup.RespondAllTemperatures(
|
||||
requestId = 1,
|
||||
temperatures = Map(
|
||||
"device1" -> DeviceGroup.Temperature(1.0),
|
||||
"device2" -> DeviceGroup.DeviceNotAvailable
|
||||
)
|
||||
))
|
||||
}
|
||||
//#query-test-stopped
|
||||
|
||||
//#query-test-stopped-later
|
||||
"return temperature reading even if device stops after answering" in {
|
||||
val requester = TestProbe()
|
||||
|
||||
val device1 = TestProbe()
|
||||
val device2 = TestProbe()
|
||||
|
||||
val queryActor = system.actorOf(DeviceGroupQuery.props(
|
||||
actorToDeviceId = Map(device1.ref -> "device1", device2.ref -> "device2"),
|
||||
requestId = 1,
|
||||
requester = requester.ref,
|
||||
timeout = 3.seconds
|
||||
))
|
||||
|
||||
device1.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
device2.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(1.0)), device1.ref)
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(2.0)), device2.ref)
|
||||
device2.ref ! PoisonPill
|
||||
|
||||
requester.expectMsg(DeviceGroup.RespondAllTemperatures(
|
||||
requestId = 1,
|
||||
temperatures = Map(
|
||||
"device1" -> DeviceGroup.Temperature(1.0),
|
||||
"device2" -> DeviceGroup.Temperature(2.0)
|
||||
)
|
||||
))
|
||||
}
|
||||
//#query-test-stopped-later
|
||||
|
||||
//#query-test-timeout
|
||||
"return DeviceTimedOut if device does not answer in time" in {
|
||||
val requester = TestProbe()
|
||||
|
||||
val device1 = TestProbe()
|
||||
val device2 = TestProbe()
|
||||
|
||||
val queryActor = system.actorOf(DeviceGroupQuery.props(
|
||||
actorToDeviceId = Map(device1.ref -> "device1", device2.ref -> "device2"),
|
||||
requestId = 1,
|
||||
requester = requester.ref,
|
||||
timeout = 1.second
|
||||
))
|
||||
|
||||
device1.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
device2.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(1.0)), device1.ref)
|
||||
|
||||
requester.expectMsg(DeviceGroup.RespondAllTemperatures(
|
||||
requestId = 1,
|
||||
temperatures = Map(
|
||||
"device1" -> DeviceGroup.Temperature(1.0),
|
||||
"device2" -> DeviceGroup.DeviceTimedOut
|
||||
)
|
||||
))
|
||||
}
|
||||
//#query-test-timeout
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
135
akka-docs/src/test/scala/tutorial_4/DeviceGroupSpec.scala
Normal file
135
akka-docs/src/test/scala/tutorial_4/DeviceGroupSpec.scala
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package tutorial_4
|
||||
|
||||
import akka.actor.PoisonPill
|
||||
import akka.testkit.{ AkkaSpec, TestProbe }
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class DeviceGroupSpec extends AkkaSpec {
|
||||
|
||||
"DeviceGroup actor" must {
|
||||
|
||||
"be able to register a device actor" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor1 = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device2"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor2 = probe.lastSender
|
||||
deviceActor1 should !==(deviceActor2)
|
||||
|
||||
// Check that the device actors are working
|
||||
deviceActor1.tell(Device.RecordTemperature(requestId = 0, 1.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 0))
|
||||
deviceActor2.tell(Device.RecordTemperature(requestId = 1, 2.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 1))
|
||||
}
|
||||
|
||||
"ignore requests for wrong groupId" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("wrongGroup", "device1"), probe.ref)
|
||||
probe.expectNoMsg(500.milliseconds)
|
||||
}
|
||||
|
||||
"return same actor for same deviceId" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor1 = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor2 = probe.lastSender
|
||||
|
||||
deviceActor1 should ===(deviceActor2)
|
||||
}
|
||||
|
||||
"be able to list active devices" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device2"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
|
||||
groupActor.tell(DeviceGroup.RequestDeviceList(requestId = 0), probe.ref)
|
||||
probe.expectMsg(DeviceGroup.ReplyDeviceList(requestId = 0, Set("device1", "device2")))
|
||||
}
|
||||
|
||||
"be able to list active devices after one shuts down" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val toShutDown = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device2"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
|
||||
groupActor.tell(DeviceGroup.RequestDeviceList(requestId = 0), probe.ref)
|
||||
probe.expectMsg(DeviceGroup.ReplyDeviceList(requestId = 0, Set("device1", "device2")))
|
||||
|
||||
probe.watch(toShutDown)
|
||||
toShutDown ! PoisonPill
|
||||
probe.expectTerminated(toShutDown)
|
||||
|
||||
groupActor.tell(DeviceGroup.RequestDeviceList(requestId = 1), probe.ref)
|
||||
probe.expectMsg(DeviceGroup.ReplyDeviceList(requestId = 1, Set("device2")))
|
||||
}
|
||||
|
||||
//#group-query-integration-test
|
||||
"be able to collect temperatures from all active devices" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor1 = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device2"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor2 = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device3"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor3 = probe.lastSender
|
||||
|
||||
// Check that the device actors are working
|
||||
deviceActor1.tell(Device.RecordTemperature(requestId = 0, 1.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 0))
|
||||
deviceActor2.tell(Device.RecordTemperature(requestId = 1, 2.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 1))
|
||||
// No temperature for device3
|
||||
|
||||
groupActor.tell(DeviceGroup.RequestAllTemperatures(requestId = 0), probe.ref)
|
||||
probe.expectMsg(
|
||||
DeviceGroup.RespondAllTemperatures(
|
||||
requestId = 0,
|
||||
temperatures = Map(
|
||||
"device1" -> DeviceGroup.Temperature(1.0),
|
||||
"device2" -> DeviceGroup.Temperature(2.0),
|
||||
"device3" -> DeviceGroup.TemperatureNotAvailable
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
//#group-query-integration-test
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
47
akka-docs/src/test/scala/tutorial_4/DeviceManager.scala
Normal file
47
akka-docs/src/test/scala/tutorial_4/DeviceManager.scala
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package tutorial_4
|
||||
|
||||
import akka.actor.{ Actor, ActorLogging, ActorRef, Props, Terminated }
|
||||
import tutorial_4.DeviceManager.RequestTrackDevice
|
||||
|
||||
object DeviceManager {
|
||||
def props(): Props = Props(new DeviceManager)
|
||||
|
||||
final case class RequestTrackDevice(groupId: String, deviceId: String)
|
||||
case object DeviceRegistered
|
||||
}
|
||||
|
||||
class DeviceManager extends Actor with ActorLogging {
|
||||
var groupIdToActor = Map.empty[String, ActorRef]
|
||||
var actorToGroupId = Map.empty[ActorRef, String]
|
||||
|
||||
override def preStart(): Unit = log.info("DeviceManager started")
|
||||
|
||||
override def postStop(): Unit = log.info("DeviceManager stopped")
|
||||
|
||||
override def receive = {
|
||||
case trackMsg @ RequestTrackDevice(groupId, _) =>
|
||||
groupIdToActor.get(groupId) match {
|
||||
case Some(ref) =>
|
||||
ref forward trackMsg
|
||||
case None =>
|
||||
log.info("Creating device group actor for {}", groupId)
|
||||
val groupActor = context.actorOf(DeviceGroup.props(groupId), "group-" + groupId)
|
||||
context.watch(groupActor)
|
||||
groupActor forward trackMsg
|
||||
groupIdToActor += groupId -> groupActor
|
||||
actorToGroupId += groupActor -> groupId
|
||||
}
|
||||
|
||||
case Terminated(groupActor) =>
|
||||
val groupId = actorToGroupId(groupActor)
|
||||
log.info("Device group actor for {} has been terminated", groupId)
|
||||
actorToGroupId -= groupActor
|
||||
groupIdToActor -= groupId
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
46
akka-docs/src/test/scala/tutorial_5/Device.scala
Normal file
46
akka-docs/src/test/scala/tutorial_5/Device.scala
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_5
|
||||
|
||||
import akka.actor.{ Actor, ActorLogging, Props }
|
||||
import tutorial_5.Device.{ ReadTemperature, RecordTemperature, RespondTemperature, TemperatureRecorded }
|
||||
import tutorial_5.DeviceManager.{ DeviceRegistered, RequestTrackDevice }
|
||||
|
||||
object Device {
|
||||
|
||||
def props(groupId: String, deviceId: String): Props = Props(new Device(groupId, deviceId))
|
||||
|
||||
final case class RecordTemperature(requestId: Long, value: Double)
|
||||
final case class TemperatureRecorded(requestId: Long)
|
||||
|
||||
final case class ReadTemperature(requestId: Long)
|
||||
final case class RespondTemperature(requestId: Long, value: Option[Double])
|
||||
}
|
||||
|
||||
class Device(groupId: String, deviceId: String) extends Actor with ActorLogging {
|
||||
var lastTemperatureReading: Option[Double] = None
|
||||
|
||||
override def preStart(): Unit = log.info("Device actor {}-{} started", groupId, deviceId)
|
||||
|
||||
override def postStop(): Unit = log.info("Device actor {}-{} stopped", groupId, deviceId)
|
||||
|
||||
override def receive: Receive = {
|
||||
case RequestTrackDevice(`groupId`, `deviceId`) =>
|
||||
sender() ! DeviceRegistered
|
||||
|
||||
case RequestTrackDevice(groupId, deviceId) =>
|
||||
log.warning(
|
||||
"Ignoring TrackDevice request for {}-{}.This actor is responsible for {}-{}.",
|
||||
groupId, deviceId, this.groupId, this.deviceId
|
||||
)
|
||||
|
||||
case RecordTemperature(id, value) =>
|
||||
log.info("Recorded temperature reading {} with {}", value, id)
|
||||
lastTemperatureReading = Some(value)
|
||||
sender() ! TemperatureRecorded(id)
|
||||
|
||||
case ReadTemperature(id) =>
|
||||
sender() ! RespondTemperature(id, lastTemperatureReading)
|
||||
}
|
||||
}
|
||||
76
akka-docs/src/test/scala/tutorial_5/DeviceGroup.scala
Normal file
76
akka-docs/src/test/scala/tutorial_5/DeviceGroup.scala
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_5
|
||||
|
||||
import akka.actor.{ Actor, ActorLogging, ActorRef, Props, Terminated }
|
||||
import tutorial_5.DeviceGroup._
|
||||
import tutorial_5.DeviceManager.RequestTrackDevice
|
||||
import scala.concurrent.duration._
|
||||
|
||||
object DeviceGroup {
|
||||
|
||||
def props(groupId: String): Props = Props(new DeviceGroup(groupId))
|
||||
|
||||
final case class RequestDeviceList(requestId: Long)
|
||||
final case class ReplyDeviceList(requestId: Long, ids: Set[String])
|
||||
|
||||
final case class RequestAllTemperatures(requestId: Long)
|
||||
final case class RespondAllTemperatures(requestId: Long, temperatures: Map[String, TemperatureReading])
|
||||
|
||||
sealed trait TemperatureReading
|
||||
final case class Temperature(value: Double) extends TemperatureReading
|
||||
case object TemperatureNotAvailable extends TemperatureReading
|
||||
case object DeviceNotAvailable extends TemperatureReading
|
||||
case object DeviceTimedOut extends TemperatureReading
|
||||
}
|
||||
|
||||
class DeviceGroup(groupId: String) extends Actor with ActorLogging {
|
||||
var deviceIdToActor = Map.empty[String, ActorRef]
|
||||
var actorToDeviceId = Map.empty[ActorRef, String]
|
||||
var nextCollectionId = 0L
|
||||
|
||||
override def preStart(): Unit = log.info("DeviceGroup {} started", groupId)
|
||||
|
||||
override def postStop(): Unit = log.info("DeviceGroup {} stopped", groupId)
|
||||
|
||||
override def receive: Receive = {
|
||||
// Note the backticks
|
||||
case trackMsg @ RequestTrackDevice(`groupId`, _) =>
|
||||
deviceIdToActor.get(trackMsg.deviceId) match {
|
||||
case Some(ref) =>
|
||||
ref forward trackMsg
|
||||
case None =>
|
||||
log.info("Creating device actor for {}", trackMsg.deviceId)
|
||||
val deviceActor = context.actorOf(Device.props(groupId, trackMsg.deviceId), "device-" + trackMsg.deviceId)
|
||||
context.watch(deviceActor)
|
||||
deviceActor forward trackMsg
|
||||
deviceIdToActor += trackMsg.deviceId -> deviceActor
|
||||
actorToDeviceId += deviceActor -> trackMsg.deviceId
|
||||
}
|
||||
|
||||
case RequestTrackDevice(groupId, deviceId) =>
|
||||
log.warning(
|
||||
"Ignoring TrackDevice request for {}. This actor is responsible for {}.",
|
||||
groupId, this.groupId
|
||||
)
|
||||
|
||||
case RequestDeviceList(requestId) =>
|
||||
sender() ! ReplyDeviceList(requestId, deviceIdToActor.keySet)
|
||||
|
||||
case Terminated(deviceActor) =>
|
||||
val deviceId = actorToDeviceId(deviceActor)
|
||||
log.info("Device actor for {} has been terminated", deviceId)
|
||||
actorToDeviceId -= deviceActor
|
||||
deviceIdToActor -= deviceId
|
||||
|
||||
case RequestAllTemperatures(requestId) =>
|
||||
context.actorOf(DeviceGroupQuery.props(
|
||||
actorToDeviceId = actorToDeviceId,
|
||||
requestId = requestId,
|
||||
requester = sender(),
|
||||
3.seconds
|
||||
))
|
||||
}
|
||||
|
||||
}
|
||||
98
akka-docs/src/test/scala/tutorial_5/DeviceGroupQuery.scala
Normal file
98
akka-docs/src/test/scala/tutorial_5/DeviceGroupQuery.scala
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_5
|
||||
|
||||
import akka.actor.Actor.Receive
|
||||
import akka.actor.{ Actor, ActorLogging, ActorRef, Props, Terminated }
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
object DeviceGroupQuery {
|
||||
|
||||
case object CollectionTimeout
|
||||
|
||||
def props(
|
||||
actorToDeviceId: Map[ActorRef, String],
|
||||
requestId: Long,
|
||||
requester: ActorRef,
|
||||
timeout: FiniteDuration
|
||||
): Props = {
|
||||
Props(new DeviceGroupQuery(actorToDeviceId, requestId, requester, timeout))
|
||||
}
|
||||
}
|
||||
|
||||
class DeviceGroupQuery(
|
||||
actorToDeviceId: Map[ActorRef, String],
|
||||
requestId: Long,
|
||||
requester: ActorRef,
|
||||
timeout: FiniteDuration
|
||||
) extends Actor with ActorLogging {
|
||||
import DeviceGroupQuery._
|
||||
import context.dispatcher
|
||||
val queryTimeoutTimer = context.system.scheduler.scheduleOnce(timeout, self, CollectionTimeout)
|
||||
|
||||
override def preStart(): Unit = {
|
||||
actorToDeviceId.keysIterator.foreach { deviceActor =>
|
||||
context.watch(deviceActor)
|
||||
deviceActor ! Device.ReadTemperature(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override def postStop(): Unit = {
|
||||
queryTimeoutTimer.cancel()
|
||||
}
|
||||
|
||||
override def receive: Receive =
|
||||
waitingForReplies(
|
||||
Map.empty,
|
||||
actorToDeviceId.keySet
|
||||
)
|
||||
|
||||
def waitingForReplies(
|
||||
repliesSoFar: Map[String, DeviceGroup.TemperatureReading],
|
||||
stillWaiting: Set[ActorRef]
|
||||
): Receive = {
|
||||
case Device.RespondTemperature(0, valueOption) =>
|
||||
val deviceActor = sender()
|
||||
val reading = valueOption match {
|
||||
case Some(value) => DeviceGroup.Temperature(value)
|
||||
case None => DeviceGroup.TemperatureNotAvailable
|
||||
}
|
||||
receivedResponse(deviceActor, reading, stillWaiting, repliesSoFar)
|
||||
|
||||
case Terminated(deviceActor) =>
|
||||
if (stillWaiting.contains(deviceActor))
|
||||
receivedResponse(deviceActor, DeviceGroup.DeviceNotAvailable, stillWaiting, repliesSoFar)
|
||||
// else ignore
|
||||
|
||||
case CollectionTimeout =>
|
||||
val timedOutReplies =
|
||||
stillWaiting.map { deviceActor =>
|
||||
val deviceId = actorToDeviceId(deviceActor)
|
||||
deviceId -> DeviceGroup.DeviceTimedOut
|
||||
}
|
||||
requester ! DeviceGroup.RespondAllTemperatures(requestId, repliesSoFar ++ timedOutReplies)
|
||||
context.stop(self)
|
||||
}
|
||||
|
||||
def receivedResponse(
|
||||
deviceActor: ActorRef,
|
||||
reading: DeviceGroup.TemperatureReading,
|
||||
stillWaiting: Set[ActorRef],
|
||||
repliesSoFar: Map[String, DeviceGroup.TemperatureReading]
|
||||
): Unit = {
|
||||
val deviceId = actorToDeviceId(deviceActor)
|
||||
val newStillWaiting = stillWaiting - deviceActor
|
||||
|
||||
val newRepliesSoFar = repliesSoFar + (deviceId -> reading)
|
||||
if (newStillWaiting.isEmpty) {
|
||||
requester ! DeviceGroup.RespondAllTemperatures(requestId, newRepliesSoFar)
|
||||
context.stop(self)
|
||||
} else {
|
||||
context.become(waitingForReplies(newRepliesSoFar, newStillWaiting))
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
157
akka-docs/src/test/scala/tutorial_5/DeviceGroupQuerySpec.scala
Normal file
157
akka-docs/src/test/scala/tutorial_5/DeviceGroupQuerySpec.scala
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_5
|
||||
|
||||
import akka.actor.PoisonPill
|
||||
import akka.testkit.{ AkkaSpec, TestProbe }
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class DeviceGroupQuerySpec extends AkkaSpec {
|
||||
|
||||
"DeviceGroupQuery" must {
|
||||
|
||||
"return temperature value for working devices" in {
|
||||
val requester = TestProbe()
|
||||
|
||||
val device1 = TestProbe()
|
||||
val device2 = TestProbe()
|
||||
|
||||
val queryActor = system.actorOf(DeviceGroupQuery.props(
|
||||
actorToDeviceId = Map(device1.ref -> "device1", device2.ref -> "device2"),
|
||||
requestId = 1,
|
||||
requester = requester.ref,
|
||||
timeout = 3.seconds
|
||||
))
|
||||
|
||||
device1.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
device2.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(1.0)), device1.ref)
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(2.0)), device2.ref)
|
||||
|
||||
requester.expectMsg(DeviceGroup.RespondAllTemperatures(
|
||||
requestId = 1,
|
||||
temperatures = Map(
|
||||
"device1" -> DeviceGroup.Temperature(1.0),
|
||||
"device2" -> DeviceGroup.Temperature(2.0)
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
"return TemperatureNotAvailable for devices with no readings" in {
|
||||
val requester = TestProbe()
|
||||
|
||||
val device1 = TestProbe()
|
||||
val device2 = TestProbe()
|
||||
|
||||
val queryActor = system.actorOf(DeviceGroupQuery.props(
|
||||
actorToDeviceId = Map(device1.ref -> "device1", device2.ref -> "device2"),
|
||||
requestId = 1,
|
||||
requester = requester.ref,
|
||||
timeout = 3.seconds
|
||||
))
|
||||
|
||||
device1.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
device2.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, None), device1.ref)
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(2.0)), device2.ref)
|
||||
|
||||
requester.expectMsg(DeviceGroup.RespondAllTemperatures(
|
||||
requestId = 1,
|
||||
temperatures = Map(
|
||||
"device1" -> DeviceGroup.TemperatureNotAvailable,
|
||||
"device2" -> DeviceGroup.Temperature(2.0)
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
"return DeviceNotAvailable if device stops before answering" in {
|
||||
val requester = TestProbe()
|
||||
|
||||
val device1 = TestProbe()
|
||||
val device2 = TestProbe()
|
||||
|
||||
val queryActor = system.actorOf(DeviceGroupQuery.props(
|
||||
actorToDeviceId = Map(device1.ref -> "device1", device2.ref -> "device2"),
|
||||
requestId = 1,
|
||||
requester = requester.ref,
|
||||
timeout = 3.seconds
|
||||
))
|
||||
|
||||
device1.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
device2.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(1.0)), device1.ref)
|
||||
device2.ref ! PoisonPill
|
||||
|
||||
requester.expectMsg(DeviceGroup.RespondAllTemperatures(
|
||||
requestId = 1,
|
||||
temperatures = Map(
|
||||
"device1" -> DeviceGroup.Temperature(1.0),
|
||||
"device2" -> DeviceGroup.DeviceNotAvailable
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
"return temperature reading even if device stops after answering" in {
|
||||
val requester = TestProbe()
|
||||
|
||||
val device1 = TestProbe()
|
||||
val device2 = TestProbe()
|
||||
|
||||
val queryActor = system.actorOf(DeviceGroupQuery.props(
|
||||
actorToDeviceId = Map(device1.ref -> "device1", device2.ref -> "device2"),
|
||||
requestId = 1,
|
||||
requester = requester.ref,
|
||||
timeout = 3.seconds
|
||||
))
|
||||
|
||||
device1.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
device2.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(1.0)), device1.ref)
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(2.0)), device2.ref)
|
||||
device2.ref ! PoisonPill
|
||||
|
||||
requester.expectMsg(DeviceGroup.RespondAllTemperatures(
|
||||
requestId = 1,
|
||||
temperatures = Map(
|
||||
"device1" -> DeviceGroup.Temperature(1.0),
|
||||
"device2" -> DeviceGroup.Temperature(2.0)
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
"return DeviceTimedOut if device does not answer in time" in {
|
||||
val requester = TestProbe()
|
||||
|
||||
val device1 = TestProbe()
|
||||
val device2 = TestProbe()
|
||||
|
||||
val queryActor = system.actorOf(DeviceGroupQuery.props(
|
||||
actorToDeviceId = Map(device1.ref -> "device1", device2.ref -> "device2"),
|
||||
requestId = 1,
|
||||
requester = requester.ref,
|
||||
timeout = 1.second
|
||||
))
|
||||
|
||||
device1.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
device2.expectMsg(Device.ReadTemperature(requestId = 0))
|
||||
|
||||
queryActor.tell(Device.RespondTemperature(requestId = 0, Some(1.0)), device1.ref)
|
||||
|
||||
requester.expectMsg(DeviceGroup.RespondAllTemperatures(
|
||||
requestId = 1,
|
||||
temperatures = Map(
|
||||
"device1" -> DeviceGroup.Temperature(1.0),
|
||||
"device2" -> DeviceGroup.DeviceTimedOut
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
133
akka-docs/src/test/scala/tutorial_5/DeviceGroupSpec.scala
Normal file
133
akka-docs/src/test/scala/tutorial_5/DeviceGroupSpec.scala
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package tutorial_5
|
||||
|
||||
import akka.actor.PoisonPill
|
||||
import akka.testkit.{ AkkaSpec, TestProbe }
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class DeviceGroupSpec extends AkkaSpec {
|
||||
|
||||
"DeviceGroup actor" must {
|
||||
|
||||
"be able to register a device actor" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor1 = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device2"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor2 = probe.lastSender
|
||||
deviceActor1 should !==(deviceActor2)
|
||||
|
||||
// Check that the device actors are working
|
||||
deviceActor1.tell(Device.RecordTemperature(requestId = 0, 1.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 0))
|
||||
deviceActor2.tell(Device.RecordTemperature(requestId = 1, 2.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 1))
|
||||
}
|
||||
|
||||
"ignore requests for wrong groupId" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("wrongGroup", "device1"), probe.ref)
|
||||
probe.expectNoMsg(500.milliseconds)
|
||||
}
|
||||
|
||||
"return same actor for same deviceId" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor1 = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor2 = probe.lastSender
|
||||
|
||||
deviceActor1 should ===(deviceActor2)
|
||||
}
|
||||
|
||||
"be able to list active devices" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device2"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
|
||||
groupActor.tell(DeviceGroup.RequestDeviceList(requestId = 0), probe.ref)
|
||||
probe.expectMsg(DeviceGroup.ReplyDeviceList(requestId = 0, Set("device1", "device2")))
|
||||
}
|
||||
|
||||
"be able to list active devices after one shuts down" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val toShutDown = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device2"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
|
||||
groupActor.tell(DeviceGroup.RequestDeviceList(requestId = 0), probe.ref)
|
||||
probe.expectMsg(DeviceGroup.ReplyDeviceList(requestId = 0, Set("device1", "device2")))
|
||||
|
||||
probe.watch(toShutDown)
|
||||
toShutDown ! PoisonPill
|
||||
probe.expectTerminated(toShutDown)
|
||||
|
||||
groupActor.tell(DeviceGroup.RequestDeviceList(requestId = 1), probe.ref)
|
||||
probe.expectMsg(DeviceGroup.ReplyDeviceList(requestId = 1, Set("device2")))
|
||||
}
|
||||
|
||||
"be able to collect temperatures from all active devices" in {
|
||||
val probe = TestProbe()
|
||||
val groupActor = system.actorOf(DeviceGroup.props("group"))
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device1"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor1 = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device2"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor2 = probe.lastSender
|
||||
|
||||
groupActor.tell(DeviceManager.RequestTrackDevice("group", "device3"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
val deviceActor3 = probe.lastSender
|
||||
|
||||
// Check that the device actors are working
|
||||
deviceActor1.tell(Device.RecordTemperature(requestId = 0, 1.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 0))
|
||||
deviceActor2.tell(Device.RecordTemperature(requestId = 1, 2.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 1))
|
||||
// No temperature for device3
|
||||
|
||||
groupActor.tell(DeviceGroup.RequestAllTemperatures(requestId = 0), probe.ref)
|
||||
probe.expectMsg(
|
||||
DeviceGroup.RespondAllTemperatures(
|
||||
requestId = 0,
|
||||
temperatures = Map(
|
||||
"device1" -> DeviceGroup.Temperature(1.0),
|
||||
"device2" -> DeviceGroup.Temperature(2.0),
|
||||
"device3" -> DeviceGroup.TemperatureNotAvailable
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
47
akka-docs/src/test/scala/tutorial_5/DeviceManager.scala
Normal file
47
akka-docs/src/test/scala/tutorial_5/DeviceManager.scala
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
|
||||
package tutorial_5
|
||||
|
||||
import akka.actor.{ Actor, ActorLogging, ActorRef, Props, Terminated }
|
||||
import tutorial_5.DeviceManager.RequestTrackDevice
|
||||
|
||||
object DeviceManager {
|
||||
def props(): Props = Props(new DeviceManager)
|
||||
|
||||
final case class RequestTrackDevice(groupId: String, deviceId: String)
|
||||
case object DeviceRegistered
|
||||
}
|
||||
|
||||
class DeviceManager extends Actor with ActorLogging {
|
||||
var groupIdToActor = Map.empty[String, ActorRef]
|
||||
var actorToGroupId = Map.empty[ActorRef, String]
|
||||
|
||||
override def preStart(): Unit = log.info("DeviceManager started")
|
||||
|
||||
override def postStop(): Unit = log.info("DeviceManager stopped")
|
||||
|
||||
override def receive = {
|
||||
case trackMsg @ RequestTrackDevice(groupId, _) =>
|
||||
groupIdToActor.get(groupId) match {
|
||||
case Some(ref) =>
|
||||
ref forward trackMsg
|
||||
case None =>
|
||||
log.info("Creating device group actor for {}", groupId)
|
||||
val groupActor = context.actorOf(DeviceGroup.props(groupId), "group-" + groupId)
|
||||
context.watch(groupActor)
|
||||
groupActor forward trackMsg
|
||||
groupIdToActor += groupId -> groupActor
|
||||
actorToGroupId += groupActor -> groupId
|
||||
}
|
||||
|
||||
case Terminated(groupActor) =>
|
||||
val groupId = actorToGroupId(groupActor)
|
||||
log.info("Device group actor for {} has been terminated", groupId)
|
||||
actorToGroupId -= groupActor
|
||||
groupIdToActor -= groupId
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
67
akka-docs/src/test/scala/tutorial_5/DeviceSpec.scala
Normal file
67
akka-docs/src/test/scala/tutorial_5/DeviceSpec.scala
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_5
|
||||
|
||||
import akka.testkit.{ AkkaSpec, TestProbe }
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
class DeviceSpec extends AkkaSpec {
|
||||
|
||||
"Device actor" must {
|
||||
|
||||
"reply to registration requests" in {
|
||||
val probe = TestProbe()
|
||||
val deviceActor = system.actorOf(Device.props("group", "device"))
|
||||
|
||||
deviceActor.tell(DeviceManager.RequestTrackDevice("group", "device"), probe.ref)
|
||||
probe.expectMsg(DeviceManager.DeviceRegistered)
|
||||
probe.lastSender should ===(deviceActor)
|
||||
}
|
||||
|
||||
"ignore wrong registration requests" in {
|
||||
val probe = TestProbe()
|
||||
val deviceActor = system.actorOf(Device.props("group", "device"))
|
||||
|
||||
deviceActor.tell(DeviceManager.RequestTrackDevice("wrongGroup", "device"), probe.ref)
|
||||
probe.expectNoMsg(500.milliseconds)
|
||||
|
||||
deviceActor.tell(DeviceManager.RequestTrackDevice("group", "Wrongdevice"), probe.ref)
|
||||
probe.expectNoMsg(500.milliseconds)
|
||||
}
|
||||
|
||||
"reply with empty reading if no temperature is known" in {
|
||||
val probe = TestProbe()
|
||||
val deviceActor = system.actorOf(Device.props("group", "device"))
|
||||
|
||||
deviceActor.tell(Device.ReadTemperature(requestId = 42), probe.ref)
|
||||
val response = probe.expectMsgType[Device.RespondTemperature]
|
||||
response.requestId should ===(42)
|
||||
response.value should ===(None)
|
||||
}
|
||||
|
||||
"reply with latest temperature reading" in {
|
||||
val probe = TestProbe()
|
||||
val deviceActor = system.actorOf(Device.props("group", "device"))
|
||||
|
||||
deviceActor.tell(Device.RecordTemperature(requestId = 1, 24.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 1))
|
||||
|
||||
deviceActor.tell(Device.ReadTemperature(requestId = 2), probe.ref)
|
||||
val response1 = probe.expectMsgType[Device.RespondTemperature]
|
||||
response1.requestId should ===(2)
|
||||
response1.value should ===(Some(24.0))
|
||||
|
||||
deviceActor.tell(Device.RecordTemperature(requestId = 3, 55.0), probe.ref)
|
||||
probe.expectMsg(Device.TemperatureRecorded(requestId = 3))
|
||||
|
||||
deviceActor.tell(Device.ReadTemperature(requestId = 4), probe.ref)
|
||||
val response2 = probe.expectMsgType[Device.RespondTemperature]
|
||||
response2.requestId should ===(4)
|
||||
response2.value should ===(Some(55.0))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
29
akka-docs/src/test/scala/tutorial_5/IotApp.scala
Normal file
29
akka-docs/src/test/scala/tutorial_5/IotApp.scala
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_5
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import tutorial_5.DeviceManager.RequestTrackDevice
|
||||
|
||||
import scala.io.StdIn
|
||||
|
||||
object IotApp {
|
||||
|
||||
def main(args: Array[String]): Unit = {
|
||||
val system = ActorSystem("iot-system")
|
||||
|
||||
try {
|
||||
// Create top level supervisor
|
||||
val supervisor = system.actorOf(DeviceManager.props(), "iot-supervisor")
|
||||
|
||||
supervisor ! RequestTrackDevice("mygroup", "device1")
|
||||
|
||||
// Exit the system after ENTER is pressed
|
||||
StdIn.readLine()
|
||||
} finally {
|
||||
system.terminate()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
24
akka-docs/src/test/scala/tutorial_5/IotSupervisor.scala
Normal file
24
akka-docs/src/test/scala/tutorial_5/IotSupervisor.scala
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package tutorial_5
|
||||
|
||||
import akka.actor.{ Actor, ActorLogging, ActorRef, Props }
|
||||
|
||||
object IotSupervisor {
|
||||
|
||||
def props(): Props = Props(new IotSupervisor)
|
||||
|
||||
}
|
||||
|
||||
class IotSupervisor extends Actor with ActorLogging {
|
||||
val deviceManager: ActorRef = context.system.actorOf(DeviceManager.props(), "device-manager")
|
||||
|
||||
override def preStart(): Unit = log.info("IoT Application started")
|
||||
|
||||
override def postStop(): Unit = log.info("IoT Application stopped")
|
||||
|
||||
// No need to handle any messages
|
||||
override def receive = Actor.emptyBehavior
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue