Skip to content
114 changes: 60 additions & 54 deletions app/web/controllers/SystemCheck.scala
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@ package web.controllers
import java.util.concurrent.TimeUnit

import com.amazonaws.services.s3.AmazonS3
import com.amazonaws.services.s3.model._
import com.mongodb.BasicDBObject
import com.mongodb.casbah.MongoDB

import org.corespring.elasticsearch.WSClient
import org.corespring.itemSearch.ElasticSearchConfig
import org.corespring.models.error.CorespringInternalError

import play.api.Play.current
import play.api.cache.Cache
import play.api.libs.concurrent.Execution.Implicits._
Expand All @@ -16,55 +19,42 @@ import play.api.mvc.{ Action, Controller }

import scala.concurrent.duration.Duration
import scala.concurrent.{ Await, Future }
import scala.util.{ Failure, Success }

class SystemCheck(s3: AmazonS3, db: MongoDB, elasticSearchConfig: ElasticSearchConfig) extends Controller {

def checkCache(): Either[CorespringInternalError, Unit] = {
Cache.set("test", "test")
Cache.get("test") match {
val timeUnit = Duration(5, TimeUnit.SECONDS)

def checkCache(setCache: String, getCache: String): Future[Either[CorespringInternalError, Unit]] = Future {
Cache.set(setCache, setCache)
Cache.get(getCache) match {
case Some(test) => if (test == "test") Right(())
else Left(CorespringInternalError("did not retrieve correct value from cache"))
case None => Left(CorespringInternalError("could not retrieve any value from cache"))
}
}

def checkS3(): Either[CorespringInternalError, Unit] = {
def checkS3(_s3: AmazonS3, bucket: String): Future[Either[CorespringInternalError, Unit]] = Future {
try {
val testBucket = "corespring-system-check"
val testObject = "circle.png"
val getS3Object = s3.getObject(testBucket, testObject)
val testBucket = new HeadBucketRequest(bucket)
_s3.headBucket(testBucket)
Right(())
} catch {
case e: Throwable => Left(CorespringInternalError("S3 is not available"))
case e: Throwable => Left(CorespringInternalError("S3 / Bucket is not available"))
}
}

def checkDatabase(): Either[CorespringInternalError, Unit] = {

val mainDb = db
val dbmodels = Seq(
"accessTokens",
"apiClients",
"contentcolls",
"fieldValues",
"itemsessions",
"orgs",
"subjects",
"users")
def checkDatabase(_db: MongoDB, dbmodels: Seq[String]): Future[Either[CorespringInternalError, Unit]] = Future {
dbmodels.foldRight[Either[CorespringInternalError, Unit]](Right(()))((dbmodel, result) => {
if (result.isRight) {
mainDb.getCollection(dbmodel).findOne() match {
case _: BasicDBObject => Right(())
case _ => Left(CorespringInternalError("could not find collection: " + dbmodel))
}
if (_db.collectionExists(dbmodel)) Right(())
else Left(CorespringInternalError("could not find collection: " + dbmodel))
} else result
})
}

def checkElasticSearch(): Either[CorespringInternalError, Unit] = {

val cfg = elasticSearchConfig
val elasticSearchClient = WSClient(cfg.url)
def checkElasticSearch(_elasticSearchConfig: ElasticSearchConfig): Future[Either[CorespringInternalError, Unit]] = Future {
val elasticSearchClient = WSClient(_elasticSearchConfig.url)
val elasticClientResult = elasticSearchClient.request("_cluster/health").get.flatMap[Either[CorespringInternalError, Unit]](response => Future {
response.status match {
case 200 => Right(())
Expand All @@ -73,28 +63,53 @@ class SystemCheck(s3: AmazonS3, db: MongoDB, elasticSearchConfig: ElasticSearchC
} recover {
case timeout: java.util.concurrent.TimeoutException => Left(CorespringInternalError("Timed out while connecting to ElasticSearch cluster"))
})
Await.result(elasticClientResult, Duration(5, TimeUnit.SECONDS));
Await.result(elasticClientResult, timeUnit);
}

def index = Action.async {

val timeout = play.api.libs.concurrent.Promise.timeout("Oops", Duration(6, TimeUnit.SECONDS))
def index = Action.async {

val timeoutCheck = play.api.libs.concurrent.Promise.timeout("Ooops", timeUnit).map {
case timeout: String => Left(CorespringInternalError("Timed out"))
}

val testCache = "test"
val bucket = "corespring-system-check"
val dbmodels = Seq(
"accessTokens",
"apiClients",
"contentcolls",
"fieldValues",
"itemsessions",
"orgs",
"subjects",
"users")

val runChecks: Future[Either[CorespringInternalError, Unit]] = scala.concurrent.Future {
val results = List(
checkS3(),
checkCache(),
checkDatabase(),
checkElasticSearch())
val futureCheckCache = checkCache(testCache,testCache)
val futureCheckS3 = checkS3(s3, bucket)
val futureCheckDatabase = checkDatabase(db, dbmodels)
val futureCheckElasticSearch = checkElasticSearch(elasticSearchConfig)

def isAnError(result: Either[CorespringInternalError, Unit]) = result match {
case Left(_) => true
case Right(_) => false
}
val errors = results.filter(isAnError)
def isAnError(result: Either[CorespringInternalError, Unit]) = result match {
case Left(_) => true
case Right(_) => false
}

if (errors.length == 0) Right()
else {
val results = Future.sequence(
Seq(
Future.firstCompletedOf(Seq(futureCheckCache, timeoutCheck)),
Future.firstCompletedOf(Seq(futureCheckS3, timeoutCheck)),
Future.firstCompletedOf(Seq(futureCheckDatabase, timeoutCheck)),
Future.firstCompletedOf(Seq(futureCheckElasticSearch, timeoutCheck))
)
)

results.map { res =>
val errors = res.filter(isAnError)

if (errors.length == 0) {
Ok
} else {
val sb = new java.lang.StringBuilder
errors.foreach { e =>
e match {
Expand All @@ -104,17 +119,8 @@ class SystemCheck(s3: AmazonS3, db: MongoDB, elasticSearchConfig: ElasticSearchC
case _ => Ok
}
}
Left(CorespringInternalError(sb.toString()))
}
}

Future.firstCompletedOf(Seq(runChecks, timeout)).map {
case timeout: String => BadRequest("timeout")
case Right(_) => Ok
case Left(error: CorespringInternalError) => {
InternalServerError(JsObject(Seq("error" -> JsString("a check failed"), "moreInfo" -> JsString(error.message))))
InternalServerError(JsObject(Seq("error" -> JsString("check(s) failed"), "moreInfo" -> JsString(sb.toString()))))
}
case Left(_) => BadRequest("An unknown error occured")
}
}
}
2 changes: 1 addition & 1 deletion project/Dependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ object Dependencies {

val amapClient = "com.rabbitmq" % "amqp-client" % "3.0.2"
val assetsLoader = ("com.ee" %% "assets-loader" % "0.12.5").exclude("com.yahoo.platform.yui", "yuicompressor")
val aws = "com.amazonaws" % "aws-java-sdk" % "1.10.0"
val aws = "com.amazonaws" % "aws-java-sdk" % "1.11.27"
val casbah = "org.mongodb" %% "casbah" % "2.6.3"
val closureCompiler = ("com.google.javascript" % "closure-compiler" % "rr2079.1" notTransitive ()).exclude("args4j", "args4j").exclude("com.google.guava", "guava").exclude("org.json", "json").exclude("com.google.protobuf", "protobuf-java").exclude("org.apache.ant", "ant").exclude("com.google.code.findbugs", "jsr305").exclude("com.googlecode.jarjar", "jarjar").exclude("junit", "junit")
val commonsCodec = "commons-codec" % "commons-codec" % "1.10"
Expand Down