diff --git a/app/EagerLoaderModule.scala b/app/EagerLoaderModule.scala index 2e1e75c..eaf51b3 100644 --- a/app/EagerLoaderModule.scala +++ b/app/EagerLoaderModule.scala @@ -1,3 +1,18 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package app import com.google.inject.AbstractModule import services.StartUpService @@ -6,7 +21,7 @@ import services.StartUpService * Run functions during request */ class EagerLoaderModule extends AbstractModule { - override def configure() = { + override def configure() : Unit = { //startupservice will run during request bind(classOf[StartUpService]).asEagerSingleton } diff --git a/app/controllers/HomeController.scala b/app/controllers/HomeController.scala index 1b1ca8a..3ff34aa 100644 --- a/app/controllers/HomeController.scala +++ b/app/controllers/HomeController.scala @@ -1,9 +1,25 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package controllers import javax.inject._ import play.api.Configuration import play.api.mvc._ import utils.BlockingHttpClient +import utils.CommonHelper import scala.concurrent.Future import scala.util.{Failure, Success} @@ -21,7 +37,7 @@ class HomeController @Inject()(configuration: Configuration, cc: ControllerCompo * will be called when the application receives a `GET` request with * a path of `/`. */ - def index = Action { + def index : Action[AnyContent] = Action { Ok(views.html.index("", "", false)) } @@ -32,11 +48,11 @@ class HomeController @Inject()(configuration: Configuration, cc: ControllerCompo */ def query(query : String) : Action[AnyContent] = Action.async { implicit request => { - val server = configuration.underlying.getString("webapi.path") - val getRequest = BlockingHttpClient.executeGet("search/"+query, server) + val server = CommonHelper.addHttpProtocolIfNotExist(CommonHelper.configuration.webApiUri) + val getRequest = BlockingHttpClient.executeGet("/search/" + query, server) getRequest match { case Success(response) => Future.successful(Ok(views.html.index(response, query, false))) - case Failure(_) => Future.successful(Ok(views.html.index("ERROR: Failed to reach server at "+server, query, true))) + case Failure(_) => Future.successful(Ok(views.html.index("ERROR: Failed to reach server at " + server, query, true))) } } } diff --git a/app/controllers/SettingsController.scala b/app/controllers/SettingsController.scala new file mode 100644 index 0000000..c2623ec --- /dev/null +++ b/app/controllers/SettingsController.scala @@ -0,0 +1,41 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package controllers + +import akka.actor.ActorSystem +import de.upb.cs.swt.delphi.webapp.BuildInfo +import javax.inject.Inject +import play.api.mvc.{Action, AnyContent, BaseController, ControllerComponents} +import utils.AppLogging +import scala.concurrent.{ExecutionContext, Future} + +class SettingsController @Inject()(val controllerComponents: ControllerComponents) extends BaseController with AppLogging{ + implicit val system: ActorSystem = ActorSystem() + implicit val ec : ExecutionContext = system.dispatcher + private val threadSleepTime:Int = 3000 // 3 second + + + //show the version of webapp service + def version: Action[AnyContent] = Action { implicit request => + val version = Ok(BuildInfo.version) + version + } + + //shutdown hook for webapp shudown + def shutDownHook: Unit = { + log.info("Webapp Stopped Successfully") + } +} diff --git a/app/services/StartUpService.scala b/app/services/StartUpService.scala index 7ebb261..214105e 100644 --- a/app/services/StartUpService.scala +++ b/app/services/StartUpService.scala @@ -1,10 +1,25 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package services import java.util.concurrent.TimeUnit import javax.inject.{Singleton, _} import play.api.inject.ApplicationLifecycle -import utils.Configuration +import utils.{CommonHelper, Configuration} import utils.instancemanagement.InstanceRegistry import scala.concurrent.duration.Duration @@ -17,28 +32,27 @@ import scala.util.{Failure, Success} @Singleton class StartUpService @Inject()(appLifecycle: ApplicationLifecycle){ - private val configuration = new Configuration() /** * Will register at the Instance Registry, get an matching WebApi instance and try to connect to it using the * /version endpoint. If successful, it will post the matching result true to the IR, otherwise false. */ def doStartUpChecks(): Unit = { + + val configuration = CommonHelper.configuration + InstanceRegistry.getWebApiVersion(configuration) match { - case Success(_) => { + case Success(_) => InstanceRegistry.sendWebApiMatchingResult(true, configuration) - } - case Failure(_) => { + case Failure(_) => InstanceRegistry.sendWebApiMatchingResult(false, configuration) - //Cannot connect to WebApi on startup, so stop execution - Await.ready(appLifecycle.stop(), Duration(5, TimeUnit.SECONDS)) - System.exit(1) - } + InstanceRegistry.handleInstanceFailure(configuration) + //Keep instance running, but webapi won't be reachable } } - + appLifecycle.addStopHook { () => - InstanceRegistry.deregister(configuration) + InstanceRegistry.handleInstanceStop(CommonHelper.configuration) Future.successful(()) } diff --git a/app/utils/AppLogging.scala b/app/utils/AppLogging.scala index 60a5878..28fa1e9 100644 --- a/app/utils/AppLogging.scala +++ b/app/utils/AppLogging.scala @@ -1,8 +1,24 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package utils import akka.actor.{ActorSystem, ExtendedActorSystem} import akka.event.{BusLogging, LoggingAdapter} trait AppLogging { - def log(implicit system: ActorSystem): LoggingAdapter = new BusLogging(system.eventStream, this.getClass.getName, this.getClass, system.asInstanceOf[ExtendedActorSystem].logFilter) + def log(implicit system: ActorSystem): LoggingAdapter = + new BusLogging(system.eventStream, this.getClass.getName, this.getClass, system.asInstanceOf[ExtendedActorSystem].logFilter) } diff --git a/app/utils/BlockingHttpClient.scala b/app/utils/BlockingHttpClient.scala index a0ff6bb..874b28b 100644 --- a/app/utils/BlockingHttpClient.scala +++ b/app/utils/BlockingHttpClient.scala @@ -1,3 +1,18 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package utils @@ -7,7 +22,7 @@ import akka.http.scaladsl.model.{HttpEntity, HttpMethods, HttpRequest, HttpRespo import akka.stream.{ActorMaterializer, ActorMaterializerSettings} import akka.util.ByteString -import scala.concurrent.{Await, Future} +import scala.concurrent.{Await, ExecutionContext, Future} import scala.concurrent.duration.Duration import scala.util.{Failure, Success, Try} import MediaTypes._ @@ -19,8 +34,8 @@ import MediaTypes._ object BlockingHttpClient { def doGet(uri : Uri) : Try[String] = { - implicit val system = ActorSystem() - implicit val executionContext = system.dispatcher + implicit val system: ActorSystem = ActorSystem() + implicit val executionContext: ExecutionContext = system.dispatcher implicit val materializer: ActorMaterializer = ActorMaterializer(ActorMaterializerSettings(system)) try { @@ -41,9 +56,9 @@ object BlockingHttpClient { } // data parameter will be """{"name":"Hello"}""" - def doPost(uri: Uri, data: String) = { - implicit val system = ActorSystem() - implicit val executionContext = system.dispatcher + def doPost(uri: Uri, data: String) : Try[String] = { + implicit val system: ActorSystem = ActorSystem() + implicit val executionContext: ExecutionContext = system.dispatcher implicit val materializer: ActorMaterializer = ActorMaterializer(ActorMaterializerSettings(system)) val bdata = ByteString(data) try { diff --git a/app/utils/CommonHelper.scala b/app/utils/CommonHelper.scala new file mode 100644 index 0000000..824d0fa --- /dev/null +++ b/app/utils/CommonHelper.scala @@ -0,0 +1,30 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +package utils + +object CommonHelper { + + val configuration: Configuration = new Configuration() + + def addHttpProtocolIfNotExist(url: String): String = { + val hasProtocol = url.startsWith("http://") || url.startsWith("https://") + if(! hasProtocol) { + "http://" + url //Default protocol is http + } else { + url + } + } +} diff --git a/app/utils/Configuration.scala b/app/utils/Configuration.scala index 9c9d8df..0225190 100644 --- a/app/utils/Configuration.scala +++ b/app/utils/Configuration.scala @@ -1,7 +1,22 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package utils import com.typesafe.config.ConfigFactory -import utils.instancemanagement.InstanceEnums.ComponentType +import utils.instancemanagement.InstanceEnums.{ComponentType, InstanceState} import utils.instancemanagement.{Instance, InstanceRegistry} import scala.util.{Failure, Success, Try} @@ -11,7 +26,7 @@ class Configuration(val bindPort: Int = ConfigFactory.load().getInt("app.portWeb val defaultWebApiPort : Int = ConfigFactory.load().getInt("webapi.port") val defaultWebApiHost : String = ConfigFactory.load().getString("webapi.host") val instanceName = "WebAppInstance" - val instanceRegistryUri : String = sys.env.getOrElse("DELPHI_WEBAPI_URI", ConfigFactory.load().getString("instance.registry.path")) + val instanceRegistryUri : String = sys.env.getOrElse("DELPHI_IR_URI", ConfigFactory.load().getString("instance.registry.path")) lazy val webApiUri:String = webApiInstance.host + ":" + webApiInstance.portNumber @@ -22,7 +37,11 @@ class Configuration(val bindPort: Int = ConfigFactory.load().getInt("app.portWeb fallbackWebApiHost, fallbackWebApiPort, "Default WebApi instance", - ComponentType.WebApi) + ComponentType.WebApi, + None, + InstanceState.Running, + List.empty[String] + ) } @@ -30,10 +49,7 @@ class Configuration(val bindPort: Int = ConfigFactory.load().getInt("app.portWeb case Some(_) => true case None => false } - lazy val assignedID : Option[Long] = InstanceRegistry.register(this) match { - case Success(id) => Some(id) - case Failure(_) => None - } + lazy val assignedID : Option[Long] = InstanceRegistry.handleInstanceStart(this) lazy val fallbackWebApiPort : Int = sys.env.get("DELPHI_WEBAPI_URI") match { case Some(hostString) => if(hostString.count(c => c == ':') == 2){ diff --git a/app/utils/JsonSupport.scala b/app/utils/JsonSupport.scala index 35bafbd..6b17a23 100644 --- a/app/utils/JsonSupport.scala +++ b/app/utils/JsonSupport.scala @@ -1,3 +1,18 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package utils import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport diff --git a/app/utils/instancemanagement/Instance.scala b/app/utils/instancemanagement/Instance.scala index 4dcfecc..d113a52 100644 --- a/app/utils/instancemanagement/Instance.scala +++ b/app/utils/instancemanagement/Instance.scala @@ -1,46 +1,93 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package utils.instancemanagement import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport -import spray.json.{DefaultJsonProtocol, JsString, JsValue, JsonFormat} +import spray.json.{DefaultJsonProtocol, DeserializationException, JsString, JsValue, JsonFormat} trait JsonSupport extends SprayJsonSupport with DefaultJsonProtocol { - implicit val componentTypeFormat = new JsonFormat[InstanceEnums.ComponentType] { + + implicit val componentTypeFormat : JsonFormat[InstanceEnums.ComponentType] = new JsonFormat[InstanceEnums.ComponentType] { + def write(compType : InstanceEnums.ComponentType) = JsString(compType.toString) - def read(value: JsValue) = value match { + def read(value: JsValue) : InstanceEnums.ComponentType = value match { case JsString(s) => s match { case "Crawler" => InstanceEnums.ComponentType.Crawler case "WebApi" => InstanceEnums.ComponentType.WebApi case "WebApp" => InstanceEnums.ComponentType.WebApp case "DelphiManagement" => InstanceEnums.ComponentType.DelphiManagement case "ElasticSearch" => InstanceEnums.ComponentType.ElasticSearch - case x => throw new RuntimeException(s"Unexpected string value $x for component type.") + case x => throw DeserializationException(s"Unexpected string value $x for component type.") + } + case y => throw DeserializationException(s"Unexpected type $y while deserializing component type.") + } + } + + implicit val stateFormat : JsonFormat[InstanceEnums.State] = new JsonFormat[InstanceEnums.State] { + + def write(compType : InstanceEnums.State) = JsString(compType.toString) + + def read(value: JsValue) : InstanceEnums.State = value match { + case JsString(s) => s match { + case "Running" => InstanceEnums.InstanceState.Running + case "Stopped" => InstanceEnums.InstanceState.Stopped + case "Failed" => InstanceEnums.InstanceState.Failed + case "Paused" => InstanceEnums.InstanceState.Paused + case "NotReachable" => InstanceEnums.InstanceState.NotReachable + case x => throw DeserializationException(s"Unexpected string value $x for instance state.") } - case y => throw new RuntimeException(s"Unexpected type $y while deserializing component type.") + case y => throw DeserializationException(s"Unexpected type $y while deserializing instance state.") } } - implicit val instanceFormat = jsonFormat5(Instance) + + implicit val instanceFormat : JsonFormat[Instance] = jsonFormat8(Instance) } final case class Instance ( id: Option[Long], host: String, - portNumber: Int, + portNumber: Long, name: String, - /* Component Type */ - componentType: InstanceEnums.ComponentType - + componentType: InstanceEnums.ComponentType, + dockerId: Option[String], + instanceState: InstanceEnums.State, + labels: List[String] ) +{ +} object InstanceEnums { type ComponentType = ComponentType.Value object ComponentType extends Enumeration { - val Crawler = Value("Crawler") - val WebApi = Value("WebApi") - val WebApp = Value("WebApp") - val DelphiManagement = Value("DelphiManagement") - val ElasticSearch = Value("ElasticSearch") + val Crawler : Value = Value("Crawler") + val WebApi : Value = Value("WebApi") + val WebApp : Value = Value("WebApp") + val DelphiManagement : Value = Value("DelphiManagement") + val ElasticSearch : Value = Value("ElasticSearch") + } + + type State = InstanceState.Value + object InstanceState extends Enumeration { + val Running : Value = Value("Running") + val Stopped : Value = Value("Stopped") + val Failed : Value = Value("Failed") + val Paused : Value = Value("Paused") + val NotReachable : Value = Value("NotReachable") } } \ No newline at end of file diff --git a/app/utils/instancemanagement/InstanceRegistry.scala b/app/utils/instancemanagement/InstanceRegistry.scala index b1d997b..6ce1d31 100644 --- a/app/utils/instancemanagement/InstanceRegistry.scala +++ b/app/utils/instancemanagement/InstanceRegistry.scala @@ -1,19 +1,35 @@ +// Copyright (C) 2018 The Delphi Team. +// See the LICENCE file distributed with this work for additional +// information regarding copyright ownership. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. package utils.instancemanagement import java.net.InetAddress import akka.actor.ActorSystem import akka.http.scaladsl.Http -import akka.http.scaladsl.marshalling.Marshal import akka.http.scaladsl.model._ import akka.http.scaladsl.unmarshalling.Unmarshal import akka.stream.ActorMaterializer -import utils.instancemanagement.InstanceEnums.ComponentType -import utils.{AppLogging, Configuration} +import akka.util.ByteString +import utils.instancemanagement.InstanceEnums.{ComponentType, InstanceState} +import utils.{AppLogging, CommonHelper, Configuration} import scala.concurrent.{Await, ExecutionContext, Future} -import scala.concurrent.duration.Duration +import scala.concurrent.duration._ import scala.util.{Failure, Success, Try} +import spray.json._ object InstanceRegistry extends JsonSupport with AppLogging { @@ -22,8 +38,83 @@ object InstanceRegistry extends JsonSupport with AppLogging implicit val materializer: ActorMaterializer = ActorMaterializer() implicit val ec : ExecutionContext = system.dispatcher + lazy val instanceIdFromEnv : Option[Long] = Try[Long](sys.env("INSTANCE_ID").toLong).toOption + + def handleInstanceStart(configuration: Configuration) : Option[Long] = { + instanceIdFromEnv match { + case Some(id) => + reportStart(configuration) match { + case Success(_) => Some(id) + case Failure(_) => None + } + case None => register(configuration) match { + case Success(id) => Some(id) + case Failure(_) => None + } + } + } + + def handleInstanceStop(configuration: Configuration): Try[Any] = { + if(instanceIdFromEnv.isDefined) { + reportStop(configuration) + } else { + deregister(configuration) + } + } + + def handleInstanceFailure(configuration: Configuration): Any = { + if(instanceIdFromEnv.isDefined) { + reportFailure(configuration) + } + } + + def reportStart(configuration: Configuration) : Try[Unit] = executeReportOperation(configuration, ReportOperationType.Start) + + def reportStop(configuration: Configuration) : Try[Unit] = { + if(configuration.usingInstanceRegistry) { + executeReportOperation(configuration, ReportOperationType.Stop) + } else { + Failure(new RuntimeException("Cannot report stop, no instance registry available.")) + } + } + + def reportFailure(configuration: Configuration) : Try[Unit] = { + if(configuration.usingInstanceRegistry){ + executeReportOperation(configuration, ReportOperationType.Failure) + } else { + Failure(new RuntimeException("Cannot report failure, no instance registry available.")) + } + } + + private def executeReportOperation(configuration: Configuration, operationType: ReportOperationType.Value) : Try[Unit] = { + instanceIdFromEnv match { + case Some(id) => + val request = HttpRequest( + method = HttpMethods.POST, + configuration.instanceRegistryUri + ReportOperationType.toOperationUriString(operationType, id)) + + Await.result(Http(system).singleRequest(request) map {response => + if(response.status == StatusCodes.OK){ + log.info(s"Successfully reported ${operationType.toString} to Instance Registry.") + Success() + } + else { + log.warning(s"Failed to report ${operationType.toString} to Instance Registry, server returned ${response.status}") + Failure(new RuntimeException(s"Failed to report ${operationType.toString} to Instance Registry, server returned ${response.status}")) + } + + } recover {case ex => + log.warning(s"Failed to report ${operationType.toString} to Instance Registry, exception: $ex") + Failure(new RuntimeException(s"Failed to report ${operationType.toString} to Instance Registry, exception: $ex")) + }, Duration.Inf) + case None => + log.warning(s"Cannot report ${operationType.toString} to Instance Registry, no instance id is present in env var 'INSTANCE_ID'.") + Failure(new RuntimeException(s"Cannot report ${operationType.toString} to Instance Registry, no instance id is present in env var 'INSTANCE_ID'.")) + } + } + def register(configuration: Configuration) : Try[Long] = { - val instance = createInstance(None,configuration.bindPort, configuration.instanceName) + val instance = createInstance(None,configuration.bindPort, configuration.instanceName, None, InstanceState.Running) Await.result(postInstance(instance, configuration.instanceRegistryUri + "/register") map {response => if(response.status == StatusCodes.OK){ @@ -52,26 +143,29 @@ object InstanceRegistry extends JsonSupport with AppLogging if(!configuration.usingInstanceRegistry) { Failure(new RuntimeException("Cannot get WebApi instance from Instance Registry, no Instance Registry available.")) } else { - val request = HttpRequest(method = HttpMethods.GET, configuration.instanceRegistryUri + "/matchingInstance?ComponentType=WebApi") + val request = HttpRequest(method = HttpMethods.GET, configuration.instanceRegistryUri + + s"/matchingInstance?Id=${configuration.assignedID.getOrElse(-1)}&ComponentType=WebApi") Await.result(Http(system).singleRequest(request) map {response => - val status = response.status - if(status == StatusCodes.OK) { - - Await.result(Unmarshal(response.entity).to[Instance] map {instance => - val webApiIP = instance.host - log.info(s"Instance Registry assigned WebApi instance at $webApiIP") - Success(instance) - } recover {case ex => - log.warning(s"Failed to read response from Instance Registry, exception: $ex") - Failure(ex) - }, Duration.Inf) - } else if ( status == StatusCodes.NotFound) { - log.warning(s"No matching instance of type 'WebApi' is present at the instance registry.") - Failure(new RuntimeException(s"Instance Registry did not contain matching instance, server returned $status")) - } else { - log.warning(s"Failed to read response from Instance Registry, server returned $status") - Failure(new RuntimeException(s"Failed to read response from Instance Registry, server returned $status")) + response.status match { + case StatusCodes.OK => + val instanceString : String = Await.result(response.entity.dataBytes.runFold(ByteString(""))(_ ++ _).map(_.utf8String), 5 seconds) + Try(instanceString.parseJson.convertTo[Instance](instanceFormat)) match { + case Success(esInstance) => + val webApiIP = esInstance.host + log.info(s"Instance Registry assigned WebApi instance at $webApiIP") + Success(esInstance) + case Failure(ex) => + log.warning(s"Failed to read response from Instance Registry, exception: $ex") + Failure(ex) + } + case StatusCodes.NotFound => + log.warning(s"No matching instance of type 'WebApi' is present at the instance registry.") + Failure(new RuntimeException(s"Instance Registry did not contain matching instance, server returned ${StatusCodes.NotFound}")) + case _ => + val status = response.status + log.warning(s"Failed to read matching instance from Instance Registry, server returned $status") + Failure(new RuntimeException(s"Failed to read matching instance from Instance Registry, server returned $status")) } } recover { case ex => log.warning(s"Failed to request WebApi instance from Instance Registry, exception: $ex ") @@ -90,7 +184,8 @@ object InstanceRegistry extends JsonSupport with AppLogging val idToPost = configuration.webApiInstance.id.getOrElse(-1L) val request = HttpRequest( method = HttpMethods.POST, - configuration.instanceRegistryUri + s"/matchingResult?Id=$idToPost&MatchingSuccessful=$isWebApiReachable") + configuration.instanceRegistryUri + + s"/matchingResult?CallerId=${configuration.assignedID.getOrElse(-1)}&MatchedInstanceId=$idToPost&MatchingSuccessful=$isWebApiReachable") Await.result(Http(system).singleRequest(request) map {response => if(response.status == StatusCodes.OK){ @@ -113,7 +208,7 @@ object InstanceRegistry extends JsonSupport with AppLogging } def getWebApiVersion(configuration: Configuration) : Try[ResponseEntity] = { - val request = HttpRequest(method = HttpMethods.GET, configuration.webApiUri + "/version") + val request = HttpRequest(method = HttpMethods.GET, CommonHelper.addHttpProtocolIfNotExist(CommonHelper.configuration.webApiUri) + "/version") Await.result(Http(system).singleRequest(request) map {response => if(response.status == StatusCodes.OK){ @@ -158,13 +253,36 @@ object InstanceRegistry extends JsonSupport with AppLogging } } - def postInstance(instance : Instance, uri: String) () : Future[HttpResponse] = - Marshal(instance).to[RequestEntity] flatMap { entity => - val request = HttpRequest(method = HttpMethods.POST, uri = uri, entity = entity) - Http(system).singleRequest(request) + def postInstance(instance : Instance, uri: String) () : Future[HttpResponse] = { + val request = HttpRequest(method = HttpMethods.POST, uri = uri, entity = instance.toJson(instanceFormat).toString()) + Try(Http(system).singleRequest(request)) match { + case Success(res) => + res + case Failure(ex) => + log.warning(s"Failed to deregister to Instance Registry, exception: $ex") + Future.failed(ex) } + } + + private def createInstance(id: Option[Long], controlPort : Int, name : String, dockerId : Option[String], instanceState: InstanceEnums.State) : Instance = + Instance(id, InetAddress.getLocalHost.getHostAddress, controlPort, name, ComponentType.WebApp, dockerId, instanceState, List.empty[String]) - private def createInstance(id: Option[Long], controlPort : Int, name : String) : Instance = - Instance(id, InetAddress.getLocalHost.getHostAddress, controlPort, name, ComponentType.WebApp) + + object ReportOperationType extends Enumeration { + val Start : Value = Value("Start") + val Stop : Value = Value("Stop") + val Failure : Value = Value("Failure") + + def toOperationUriString(operation: ReportOperationType.Value, id: Long) : String = { + operation match { + case Start => + s"/reportStart?Id=$id" + case Stop => + s"/reportStop?Id=$id" + case _ => + s"/reportFailure?Id=$id" + } + } + } } \ No newline at end of file diff --git a/conf/routes b/conf/routes index 9d5a3ce..a00b043 100644 --- a/conf/routes +++ b/conf/routes @@ -5,6 +5,7 @@ # An example controller showing a sample home page GET / controllers.HomeController.index GET /search/ controllers.HomeController.query(query) + GET /version controllers.SettingsController.version # Map static resources from the /public folder to the /assets URL path GET /assets/*file controllers.Assets.versioned(path="/public", file: Asset) \ No newline at end of file diff --git a/project/scalastyle-config.xml b/project/scalastyle-config.xml index c220edc..88ff84c 100644 --- a/project/scalastyle-config.xml +++ b/project/scalastyle-config.xml @@ -8,8 +8,8 @@ -