/examples/cluster-monitor/src/main/scala/pl/caltha/akka/cluster/monitor/frontend/Static.scala

https://github.com/rkrzewski/akka-cluster-etcd · Scala · 106 lines · 87 code · 17 blank · 2 comment · 0 complexity · c2b6dc33c862191ad3bb41d6326565e3 MD5 · raw file

  1. package pl.caltha.akka.cluster.monitor.frontend
  2. import scala.concurrent.duration._
  3. import scala.util.Failure
  4. import scala.util.Success
  5. import akka.NotUsed
  6. import akka.actor.Actor
  7. import akka.actor.ActorLogging
  8. import akka.actor.AddressFromURIString
  9. import akka.cluster.ClusterEvent._
  10. import akka.cluster.MemberFactory
  11. import akka.cluster.MemberStatus
  12. import akka.http.scaladsl.Http
  13. import akka.http.scaladsl.model.HttpRequest
  14. import akka.http.scaladsl.model.HttpResponse
  15. import akka.http.scaladsl.model.ws.BinaryMessage
  16. import akka.http.scaladsl.model.ws.Message
  17. import akka.http.scaladsl.model.ws.TextMessage
  18. import akka.http.scaladsl.model.ws.UpgradeToWebSocket
  19. import akka.http.scaladsl.server.Directives._
  20. import akka.http.scaladsl.server.Route
  21. import akka.stream.ActorMaterializer
  22. import akka.stream.Materializer
  23. import akka.stream.SourceShape
  24. import akka.stream.scaladsl.Flow
  25. import akka.stream.scaladsl.Sink
  26. import akka.stream.scaladsl.Source
  27. import akka.stream.scaladsl.GraphDSL
  28. import akka.stream.scaladsl.Zip
  29. import spray.json._
  30. class Static extends Actor with ActorLogging {
  31. implicit val actorSystem = context.system
  32. implicit val executionContext = actorSystem.dispatcher
  33. implicit val materializer: Materializer = ActorMaterializer()
  34. def receive = Actor.ignoringBehavior
  35. val binding = Http().bindAndHandle(routes, "0.0.0.0", 8080)
  36. binding.onComplete {
  37. case Success(b) log.info("Started HTTP server at {}", b.localAddress)
  38. case Failure(t) log.error(t, "Failed to start HTTP server")
  39. }
  40. // drain data from any incoming message
  41. val wsSink: Sink[Message, _] = Flow[Message].mapAsync(1)(_ match {
  42. case m: TextMessage
  43. m.textStream.runWith(Sink.ignore)
  44. case m: BinaryMessage
  45. m.dataStream.runWith(Sink.ignore)
  46. }).to(Sink.ignore)
  47. val scenario: Seq[ClusterDomainEvent] = Seq(
  48. MemberUp(MemberFactory("akka.tcp://Main@172.17.0.3:2552", Set("frontend"), MemberStatus.up)),
  49. LeaderChanged(Some(AddressFromURIString("akka.tcp://Main@172.17.0.3:2552"))),
  50. RoleLeaderChanged("frontend", Some(AddressFromURIString("akka.tcp://Main@172.17.0.3:2552"))),
  51. MemberUp(MemberFactory("akka.tcp://Main@172.17.0.4:2552", Set("backend"), MemberStatus.up)),
  52. RoleLeaderChanged("backend", Some(AddressFromURIString("akka.tcp://Main@172.17.0.4:2552"))),
  53. MemberUp(MemberFactory("akka.tcp://Main@172.17.0.5:2552", Set("backend"), MemberStatus.up)),
  54. UnreachableMember(MemberFactory("akka.tcp://Main@172.17.0.5:2552", Set("backend"), MemberStatus.up)),
  55. ReachableMember(MemberFactory("akka.tcp://Main@172.17.0.5:2552", Set("backend"), MemberStatus.up)),
  56. UnreachableMember(MemberFactory("akka.tcp://Main@172.17.0.5:2552", Set("backend"), MemberStatus.up)),
  57. MemberRemoved(MemberFactory("akka.tcp://Main@172.17.0.5:2552", Set("backend"), MemberStatus.removed), MemberStatus.down))
  58. def jsonEncoder[T: JsonWriter]: Flow[T, Message, NotUsed] =
  59. Flow[T].map(e TextMessage.Strict(e.toJson.compactPrint))
  60. // send events from scenario in an infinite loop, once every 5 seconds
  61. val wsSource: Source[Message, _] = Source.fromGraph(
  62. GraphDSL.create() { implicit b
  63. import GraphDSL.Implicits._
  64. import JsonProtocol._
  65. val welcomeSource = Source.single(WelcomeMessage(AddressFromURIString("akka.tcp://Main@172.17.0.3:2552"))).via(jsonEncoder)
  66. val eventsSource = Source.repeat(()).mapConcat(_ scenario.to[collection.immutable.Iterable]).via(jsonEncoder)
  67. val messages = b.add(welcomeSource.concat(eventsSource))
  68. val ticks = b.add(Source.tick(0.seconds, 5.seconds, ()))
  69. val zip = b.add(Zip[Message, Unit])
  70. val extract = b.add(Flow[(Message, Unit)].map { case (e, _) e })
  71. messages ~> zip.in0
  72. ticks ~> zip.in1
  73. zip.out ~> extract
  74. SourceShape(extract.outlet)
  75. })
  76. private def wsHandler: HttpRequest HttpResponse = {
  77. case req: HttpRequest req.header[UpgradeToWebSocket] match {
  78. case Some(upgrade)
  79. upgrade.handleMessagesWithSinkSource(wsSink, wsSource)
  80. case None HttpResponse(400, entity = "Missing Upgrade header")
  81. }
  82. }
  83. def routes: Route =
  84. path("") {
  85. getFromResource("public/index.html")
  86. } ~ getFromResourceDirectory("public") ~ path("ws") {
  87. handleWith(wsHandler)
  88. }
  89. }