From 135f80ba71454d777ceb0437175a50c16f7ac339 Mon Sep 17 00:00:00 2001
From: Aron Engineer <aron.engineer1@gmail.com>
Date: Wed, 24 Feb 2021 21:23:07 +0000
Subject: [PATCH] WIP: Http endpoint added for health check

---
 akka-bbb-apps/project/Dependencies.scala      |  8 ++++
 .../scala/org/bigbluebutton/ApiService.scala  | 31 +++++++++++++
 .../main/scala/org/bigbluebutton/Boot.scala   | 14 +++++-
 .../bigbluebutton/SystemConfiguration.scala   |  4 ++
 .../service/HealthzService.scala              | 45 +++++++++++++++++++
 .../src/universal/conf/application.conf       |  4 +-
 6 files changed, 103 insertions(+), 3 deletions(-)
 mode change 100644 => 100755 akka-bbb-apps/project/Dependencies.scala
 create mode 100755 akka-bbb-apps/src/main/scala/org/bigbluebutton/ApiService.scala
 create mode 100755 akka-bbb-apps/src/main/scala/org/bigbluebutton/service/HealthzService.scala

diff --git a/akka-bbb-apps/project/Dependencies.scala b/akka-bbb-apps/project/Dependencies.scala
old mode 100644
new mode 100755
index 576e5fb2a5..6e69b45e36
--- a/akka-bbb-apps/project/Dependencies.scala
+++ b/akka-bbb-apps/project/Dependencies.scala
@@ -14,6 +14,7 @@ object Dependencies {
 
     // Libraries
     val akkaVersion = "2.5.19"
+    val akkaHttpVersion = "10.1.4"
     val gson = "2.8.5"
     val jackson = "2.9.7"
     val logback = "1.2.3"
@@ -47,6 +48,10 @@ object Dependencies {
     val commonsCodec = "commons-codec" % "commons-codec" % Versions.codec
     val sprayJson = "io.spray" % "spray-json_2.12" % Versions.spray
 
+    val akkaStream = "com.typesafe.akka" %% "akka-stream" % Versions.akkaVersion
+    val akkaHttp = "com.typesafe.akka" %% "akka-http" % Versions.akkaHttpVersion
+    val akkaHttpSprayJson = "com.typesafe.akka" %% "akka-http-spray-json" % Versions.akkaHttpVersion
+
     val apacheLang = "org.apache.commons" % "commons-lang3" % Versions.lang
 
     val bbbCommons = "org.bigbluebutton" % "bbb-common-message_2.12" % Versions.bbbCommons excludeAll (
@@ -73,6 +78,7 @@ object Dependencies {
     Compile.scalaCompiler,
     Compile.akkaActor,
     Compile.akkaSl4fj,
+    Compile.akkaStream,
     Compile.googleGson,
     Compile.jacksonModule,
     Compile.quicklens,
@@ -80,5 +86,7 @@ object Dependencies {
     Compile.commonsCodec,
     Compile.sprayJson,
     Compile.apacheLang,
+    Compile.akkaHttp,
+    Compile.akkaHttpSprayJson,
     Compile.bbbCommons) ++ testing
 }
\ No newline at end of file
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/ApiService.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/ApiService.scala
new file mode 100755
index 0000000000..df07205d50
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/ApiService.scala
@@ -0,0 +1,31 @@
+package org.bigbluebutton
+
+import akka.http.scaladsl.model._
+import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
+import akka.http.scaladsl.server.Directives._
+import org.bigbluebutton.service.HealthzService
+import spray.json.DefaultJsonProtocol
+
+case class HealthResponse(isHealthy: Boolean)
+
+trait JsonSupportProtocol extends SprayJsonSupport with DefaultJsonProtocol {
+  implicit val healthServiceJsonFormat = jsonFormat1(HealthResponse)
+}
+
+class ApiService(healthz: HealthzService) extends JsonSupportProtocol {
+
+  def routes =
+    path("healthz") {
+      get {
+        val future = healthz.getHealthz()
+        onSuccess(future) {
+          case response =>
+            if (response.isHealthy) {
+              complete(StatusCodes.OK, HealthResponse(response.isHealthy))
+            } else {
+              complete(StatusCodes.ServiceUnavailable, HealthResponse(response.isHealthy))
+            }
+        }
+      }
+    }
+}
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/Boot.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/Boot.scala
index 6f1eb2b0be..f28b706626 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/Boot.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/Boot.scala
@@ -1,5 +1,9 @@
 package org.bigbluebutton
 
+import akka.actor.ActorSystem
+import akka.event.Logging
+import akka.http.scaladsl.Http
+import akka.stream.ActorMaterializer
 import org.bigbluebutton.common2.redis.{ MessageSender, RedisConfig, RedisPublisher }
 import org.bigbluebutton.core._
 import org.bigbluebutton.core.bus._
@@ -8,13 +12,13 @@ import org.bigbluebutton.core2.AnalyticsActor
 import org.bigbluebutton.core2.FromAkkaAppsMsgSenderActor
 import org.bigbluebutton.endpoint.redis.AppsRedisSubscriberActor
 import org.bigbluebutton.endpoint.redis.RedisRecorderActor
-import akka.actor.ActorSystem
-import akka.event.Logging
 import org.bigbluebutton.common2.bus.IncomingJsonMessageBus
+import org.bigbluebutton.service.HealthzService
 
 object Boot extends App with SystemConfiguration {
 
   implicit val system = ActorSystem("bigbluebutton-apps-system")
+  implicit val materializer: ActorMaterializer = ActorMaterializer()
   implicit val executor = system.dispatcher
 
   val logger = Logging(system, getClass)
@@ -75,4 +79,10 @@ object Boot extends App with SystemConfiguration {
     ),
     "redis-subscriber"
   )
+
+  val healthz = HealthzService(system)
+
+  val apiService = new ApiService(healthz)
+
+  val bindingFuture = Http().bindAndHandle(apiService.routes, httpHost, httpPort)
 }
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala
index 54c67fecf2..640e516aad 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala
@@ -74,4 +74,8 @@ trait SystemConfiguration {
   lazy val toAkkaTranscodeJsonChannel = Try(config.getString("eventBus.toAkkaTranscodeJsonChannel")).getOrElse("to-akka-transcode-json-channel")
   lazy val fromAkkaTranscodeJsonChannel = Try(config.getString("eventBus.fromAkkaTranscodeJsonChannel")).getOrElse("from-akka-transcode-json-channel")
 
+  // Grab the "interface" parameter from the http config
+  val httpHost = config.getString("http.interface")
+  // Grab the "port" parameter from the http config
+  val httpPort = config.getInt("http.port")
 }
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/service/HealthzService.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/service/HealthzService.scala
new file mode 100755
index 0000000000..2e07689b41
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/service/HealthzService.scala
@@ -0,0 +1,45 @@
+package org.bigbluebutton.service
+
+import akka.actor.{ Actor, ActorLogging, ActorSystem, Props }
+import akka.util.Timeout
+
+import scala.concurrent.Future
+import scala.concurrent.duration.DurationInt
+import akka.pattern.{ AskTimeoutException, ask }
+
+sealed trait HealthMessage
+
+case object GetHealthMessage extends HealthMessage
+case class GetHealthResponseMessage(isHealthy: Boolean) extends HealthMessage
+
+object HealthzService {
+  def apply(system: ActorSystem) = new HealthzService(system)
+}
+
+class HealthzService(system: ActorSystem) {
+  implicit def executionContext = system.dispatcher
+  implicit val timeout: Timeout = 2 seconds
+
+  val actorRef = system.actorOf(HealthzActor.props())
+
+  def getHealthz(): Future[GetHealthResponseMessage] = {
+    val future = actorRef.ask(GetHealthMessage).mapTo[GetHealthResponseMessage]
+    future.recover {
+      case e: AskTimeoutException => {
+        GetHealthResponseMessage(isHealthy = false)
+      }
+    }
+  }
+
+}
+
+object HealthzActor {
+  def props(): Props = Props(classOf[HealthzActor])
+}
+
+class HealthzActor extends Actor with ActorLogging {
+  override def receive: Receive = {
+    case GetHealthMessage => sender ! GetHealthResponseMessage(isHealthy = true)
+    case _                => println("unexpected message, exception could be raised")
+  }
+}
\ No newline at end of file
diff --git a/akka-bbb-apps/src/universal/conf/application.conf b/akka-bbb-apps/src/universal/conf/application.conf
index 564b152038..00d19d3a65 100755
--- a/akka-bbb-apps/src/universal/conf/application.conf
+++ b/akka-bbb-apps/src/universal/conf/application.conf
@@ -61,7 +61,9 @@ eventBus {
 
 http {
   interface = "127.0.0.1"
-  port = 9999
+  interface = ${?INTERFACE}
+  port = 8901
+  port = ${?PORT}
 }
 
 services {
-- 
GitLab