diff --git a/modules/api/src/main/scala/vinyldns/api/Boot.scala b/modules/api/src/main/scala/vinyldns/api/Boot.scala index 854c5b3d9..e29b374b7 100644 --- a/modules/api/src/main/scala/vinyldns/api/Boot.scala +++ b/modules/api/src/main/scala/vinyldns/api/Boot.scala @@ -33,8 +33,9 @@ import vinyldns.api.domain.membership._ import vinyldns.api.domain.record.RecordSetService import vinyldns.api.domain.zone._ import vinyldns.api.repository.{ApiDataAccessor, ApiDataAccessorProvider, TestDataLoader} -import vinyldns.api.route.{HealthService, VinylDNSService} +import vinyldns.api.route.VinylDNSService import vinyldns.core.VinylDNSMetrics +import vinyldns.core.health.HealthService import vinyldns.core.queue.{MessageCount, MessageQueueLoader} import vinyldns.core.repository.DataStoreLoader diff --git a/modules/api/src/main/scala/vinyldns/api/route/HealthCheckRouting.scala b/modules/api/src/main/scala/vinyldns/api/route/HealthCheckRouting.scala index 39f829652..aaf362be6 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/HealthCheckRouting.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/HealthCheckRouting.scala @@ -19,6 +19,7 @@ package vinyldns.api.route import akka.http.scaladsl.model.StatusCodes import akka.http.scaladsl.server.{Directives, Route} import akka.util.Timeout +import vinyldns.core.health.HealthService import scala.concurrent.duration._ diff --git a/modules/api/src/main/scala/vinyldns/api/route/VinylDNSService.scala b/modules/api/src/main/scala/vinyldns/api/route/VinylDNSService.scala index 81cc83b12..e0f644f0f 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/VinylDNSService.scala +++ b/modules/api/src/main/scala/vinyldns/api/route/VinylDNSService.scala @@ -31,6 +31,7 @@ import vinyldns.api.domain.membership.MembershipServiceAlgebra import vinyldns.api.domain.record.RecordSetServiceAlgebra import vinyldns.api.domain.zone.ZoneServiceAlgebra import vinyldns.core.domain.membership.{MembershipRepository, UserRepository} +import vinyldns.core.health.HealthService import scala.util.matching.Regex diff --git a/modules/api/src/test/scala/vinyldns/api/route/HealthCheckRoutingSpec.scala b/modules/api/src/test/scala/vinyldns/api/route/HealthCheckRoutingSpec.scala index 1e0361c43..93a584e23 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/HealthCheckRoutingSpec.scala +++ b/modules/api/src/test/scala/vinyldns/api/route/HealthCheckRoutingSpec.scala @@ -23,6 +23,7 @@ import org.scalatest.mockito.MockitoSugar import org.scalatest.{Matchers, OneInstancePerTest, WordSpec} import cats.effect._ import vinyldns.core.health.HealthCheck.HealthCheckError +import vinyldns.core.health.HealthService class HealthCheckRoutingSpec extends WordSpec diff --git a/modules/api/src/main/scala/vinyldns/api/route/HealthService.scala b/modules/core/src/main/scala/vinyldns/core/health/HealthService.scala similarity index 97% rename from modules/api/src/main/scala/vinyldns/api/route/HealthService.scala rename to modules/core/src/main/scala/vinyldns/core/health/HealthService.scala index 3d11b1262..674b8ae89 100644 --- a/modules/api/src/main/scala/vinyldns/api/route/HealthService.scala +++ b/modules/core/src/main/scala/vinyldns/core/health/HealthService.scala @@ -14,7 +14,7 @@ * limitations under the License. */ -package vinyldns.api.route +package vinyldns.core.health import cats.effect.{ContextShift, IO} import cats.implicits._ diff --git a/modules/api/src/test/scala/vinyldns/api/route/HealthServiceSpec.scala b/modules/core/src/test/scala/vinyldns/core/health/HealthServiceSpec.scala similarity index 91% rename from modules/api/src/test/scala/vinyldns/api/route/HealthServiceSpec.scala rename to modules/core/src/test/scala/vinyldns/core/health/HealthServiceSpec.scala index 167112f85..0577379f1 100644 --- a/modules/api/src/test/scala/vinyldns/api/route/HealthServiceSpec.scala +++ b/modules/core/src/test/scala/vinyldns/core/health/HealthServiceSpec.scala @@ -14,14 +14,13 @@ * limitations under the License. */ -package vinyldns.api.route +package vinyldns.core.health -import org.scalatest.{Matchers, WordSpec} -import vinyldns.api.ResultHelpers import cats.effect._ +import org.scalatest.{Matchers, WordSpec} import vinyldns.core.health.HealthCheck._ -class HealthServiceSpec extends WordSpec with Matchers with ResultHelpers { +class HealthServiceSpec extends WordSpec with Matchers { "Checking Status" should { val successCheck: HealthCheck = IO.unit.attempt.asHealthCheck diff --git a/modules/portal/app/controllers/HealthController.scala b/modules/portal/app/controllers/HealthController.scala new file mode 100644 index 000000000..942f517d5 --- /dev/null +++ b/modules/portal/app/controllers/HealthController.scala @@ -0,0 +1,38 @@ +/* + * Copyright 2018 Comcast Cable Communications Management, LLC + * + * 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.{Inject, Singleton} +import play.api.mvc.{AbstractController, Action, AnyContent, ControllerComponents} +import vinyldns.core.health.HealthService + +@Singleton +class HealthController @Inject()(components: ControllerComponents, healthService: HealthService) + extends AbstractController(components) + with CacheHeader { + + def health(): Action[AnyContent] = Action { implicit request => + healthService + .checkHealth() + .map { + case Nil => Ok("OK").withHeaders(cacheHeaders: _*) + case _ => + InternalServerError("There was an internal server error.").withHeaders(cacheHeaders: _*) + } + .unsafeRunSync() + } +} diff --git a/modules/portal/app/modules/VinylDNSModule.scala b/modules/portal/app/modules/VinylDNSModule.scala index aed89631b..2e366da6d 100644 --- a/modules/portal/app/modules/VinylDNSModule.scala +++ b/modules/portal/app/modules/VinylDNSModule.scala @@ -38,6 +38,7 @@ import controllers.repository.{PortalDataAccessor, PortalDataAccessorProvider} import play.api.{Configuration, Environment} import vinyldns.core.crypto.CryptoAlgebra import vinyldns.core.domain.membership.{UserChangeRepository, UserRepository} +import vinyldns.core.health.HealthService import vinyldns.core.repository.DataStoreLoader class VinylDNSModule(environment: Environment, configuration: Configuration) extends AbstractModule { @@ -57,6 +58,7 @@ class VinylDNSModule(environment: Environment, configuration: Configuration) bind(classOf[Authenticator]).toInstance(authenticator()) bind(classOf[UserRepository]).toInstance(repositories.userRepository) bind(classOf[UserChangeRepository]).toInstance(repositories.userChangeRepository) + bind(classOf[HealthService]).toInstance(new HealthService(loaderResponse.healthChecks)) } startApp.unsafeRunSync() diff --git a/modules/portal/conf/routes b/modules/portal/conf/routes index b0280c5b6..861b92493 100644 --- a/modules/portal/conf/routes +++ b/modules/portal/conf/routes @@ -17,6 +17,7 @@ GET /batchchanges/new @controllers.FrontendController GET /batchchanges/:id @controllers.FrontendController.viewBatchChange(id: String) ### Routes to process requests, get data ### +GET /health @controllers.HealthController.health POST /login @controllers.VinylDNS.login GET /download-creds-file/:fileName @controllers.VinylDNS.serveCredsFile(fileName) POST /regenerate-creds @controllers.VinylDNS.regenerateCreds diff --git a/modules/portal/test/controllers/HealthControllerSpec.scala b/modules/portal/test/controllers/HealthControllerSpec.scala new file mode 100644 index 000000000..969a6a3ae --- /dev/null +++ b/modules/portal/test/controllers/HealthControllerSpec.scala @@ -0,0 +1,58 @@ +/* + * Copyright 2018 Comcast Cable Communications Management, LLC + * + * 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 cats.effect.IO +import org.junit.runner.RunWith +import org.specs2.mutable.Specification +import org.specs2.runner.JUnitRunner +import play.api.mvc.ControllerComponents +import play.api.test.Helpers.{GET, status} +import play.api.test.{FakeRequest, Helpers} +import vinyldns.core.health.HealthService +import vinyldns.core.health.HealthCheck._ +import play.api.test.Helpers._ + +@RunWith(classOf[JUnitRunner]) +class HealthControllerSpec extends Specification { + + val components: ControllerComponents = Helpers.stubControllerComponents() + + "HealthController" should { + "send 200 if the healthcheck succeeds" in { + val healthService = new HealthService(List(IO.unit.attempt.asHealthCheck)) + val controller = new HealthController(components, healthService) + + val result = controller + .health() + .apply(FakeRequest(GET, "/health")) + + status(result) must beEqualTo(200) + } + "send 500 if a healthcheck fails" in { + val err = IO.raiseError(new RuntimeException("bad!!")).attempt.asHealthCheck + val healthService = new HealthService(List(IO.unit.attempt.asHealthCheck, err)) + val controller = new HealthController(components, healthService) + + val result = controller + .health() + .apply(FakeRequest(GET, "/health")) + + status(result) must beEqualTo(500) + } + } +} diff --git a/modules/portal/test/controllers/TestApplicationData.scala b/modules/portal/test/controllers/TestApplicationData.scala index 9620be7ce..3e1d1d545 100644 --- a/modules/portal/test/controllers/TestApplicationData.scala +++ b/modules/portal/test/controllers/TestApplicationData.scala @@ -26,6 +26,8 @@ import play.api.libs.json.{JsObject, JsValue, Json} import vinyldns.core.crypto.{CryptoAlgebra, NoOpCrypto} import vinyldns.core.domain.membership._ import vinyldns.core.domain.record._ +import vinyldns.core.health.HealthService + import scala.util.Success trait TestApplicationData { this: Mockito => @@ -239,7 +241,8 @@ trait TestApplicationData { this: Mockito => bind[Authenticator].to(mockAuth), bind[UserRepository].to(mockUserRepo), bind[UserChangeRepository].to(mockUserChangeRepo), - bind[CryptoAlgebra].to(new NoOpCrypto()) + bind[CryptoAlgebra].to(new NoOpCrypto()), + bind[HealthService].to(new HealthService(List())) ) .configure(testConfig) .build()