diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..549e2ed --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/project/project/ +/project/target/ +/target/ +.idea \ No newline at end of file diff --git a/README.md b/README.md index 2440df0..267be97 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,24 @@ -# server-agent -A demo Scala service for ISLE: Introduction to Scala Language and Ecosystem +# Server HealthCheck Agent +## Overview +Server HealthCheck Agent is a daemon reporting host health checks to console. Current implementation +supports two modes of operation: + * heartbeat - agent sends heartbeat messages to console with specified frequency + * file check - agent checks for file presence in specified location with specified frequency + +Program arguments: + * `--type` can be one of `heartbeat` or `filecheck` + * `--frequency` time period for repeatable check in seconds + * `--location` (mandatory for `filecheck` mode only) - location of a file to check for existence + +## Building and running +Agent is using SBT for building and packaging the code. To build a fatjar with all dependencies included run: + + sbt clean assembly + +When agent is packaged it can be executed with `java -jar `. E.g. from project root: + + #heartbeat mode + java -jar target/scala-2.12/agent.jar --type heartbeat --frequency 5 + + #file check mode + java -jar target/scala-2.12/agent.jar --type filecheck --location --frequency 5 \ No newline at end of file diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..3a16628 --- /dev/null +++ b/build.sbt @@ -0,0 +1,14 @@ +import Dependencies._ + +lazy val root = Project(id = "agent", file(".")) + .settings( + inThisBuild(List( + organization := "io.datastrophic", + scalaVersion := "2.12.4", + version := "0.1.0-SNAPSHOT" + )), + name := "agent", + libraryDependencies += scalaTest % Test, + mainClass in assembly := Some("io.datastrophic.isle.agent.Agent"), + assemblyJarName in assembly := "agent.jar" + ) \ No newline at end of file diff --git a/project/Dependencies.scala b/project/Dependencies.scala new file mode 100644 index 0000000..b3496b8 --- /dev/null +++ b/project/Dependencies.scala @@ -0,0 +1,5 @@ +import sbt._ + +object Dependencies { + lazy val scalaTest = "org.scalatest" %% "scalatest" % "3.0.4" +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..8b697bb --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.1.0 diff --git a/project/plugins.sbt b/project/plugins.sbt new file mode 100644 index 0000000..09c90ca --- /dev/null +++ b/project/plugins.sbt @@ -0,0 +1 @@ +addSbtPlugin("com.eed3si9n" % "sbt-assembly" % "0.14.6") \ No newline at end of file diff --git a/src/main/scala/io/datastrophic/isle/agent/Agent.scala b/src/main/scala/io/datastrophic/isle/agent/Agent.scala new file mode 100644 index 0000000..6c51c1b --- /dev/null +++ b/src/main/scala/io/datastrophic/isle/agent/Agent.scala @@ -0,0 +1,28 @@ +package io.datastrophic.isle.agent + +import ArgumentParser._ + +object Agent extends App { + println("agent started. parsing arguments") + val (arguments, error) = parseArguments(args) + + if(error.nonEmpty){ + println(error) + } else { + if(arguments(TypeKey) == FileCheckValue) { + println("running filecheck") + val check = new FileCheck(arguments(LocationKey)) + while (true) { + check.checkFile() + Thread.sleep(arguments(FrequencyKey).toLong * 1000) + } + } else { + println("running heartbeat") + val check = new HeartBeatCheck + while (true) { + check.heartbeat() + Thread.sleep(arguments(FrequencyKey).toLong * 1000) + } + } + } +} diff --git a/src/main/scala/io/datastrophic/isle/agent/ArgumentParser.scala b/src/main/scala/io/datastrophic/isle/agent/ArgumentParser.scala new file mode 100644 index 0000000..5e779e7 --- /dev/null +++ b/src/main/scala/io/datastrophic/isle/agent/ArgumentParser.scala @@ -0,0 +1,79 @@ +package io.datastrophic.isle.agent + +object ArgumentParser { + val FrequencyKey = "--frequency" + val TypeKey = "--type" + val FileCheckValue = "filecheck" + val HeartbeatCheckValue = "heartbeat" + val LocationKey = "--location" + + val usage = + """ + | heartbeat mode: java -jar agent.jar --type heartbeat --frequency 5 + | file check mode: java -jar agent.jar --type filecheck --location --frequency 5 + """.stripMargin + + + /** + * @param args array of GNU-style arguments + * @return tuple that contains a map of arguments and an error message in case of parsing problem + */ + def parseArguments(args: Array[String]): (Map[String, String], String) = { + var i = 0 + var error = "" + var arguments = Map.empty[String, String] + + while (i < args.length){ + if(!args(i).startsWith("--")){ + arguments = Map() + error = s"Argument name doesn't conform GNU style: [${args(i)}]. Usage: $usage" + i = args.length + } else if (i + 1 == args.length){ + arguments = Map() + error = s"Argument not specified: [${args(i)}]. Usage: $usage" + i = args.length + } else { + arguments += args(i) -> args(i+1) + i = i+2 + } + } + + if(error.isEmpty){ + val validation = validate(arguments) + + if(validation._1){ + (arguments, error) + } else { + (arguments, validation._2) + } + } else { + (Map(), error) + } + + } + + /** + * Method provides validation for parsed arguments Map + * @param args Map of parsed arguments + * @return tuple that contains a result of validation and an error message in case of validation failure + */ + def validate(args: Map[String, String]): (Boolean, String) = { + if(args.get(FrequencyKey).isDefined && args(FrequencyKey).forall(_.isDigit)){ + if(args.get(TypeKey).isDefined && (args(TypeKey) == FileCheckValue || args(TypeKey) == HeartbeatCheckValue)){ + if(args(TypeKey) == FileCheckValue) { + if (args.get(LocationKey).isDefined) { + (true, "") + } else { + (false, s"$LocationKey is missing value for the FileCheck. Usage: $usage") + } + } else { + (true, "") + } + } else { + (false, s"$TypeKey key is missing or has invalid value. Usage: $usage") + } + } else { + (false, s"$FrequencyKey key is missing or not a digit. Usage: $usage") + } + } +} diff --git a/src/main/scala/io/datastrophic/isle/agent/FileCheck.scala b/src/main/scala/io/datastrophic/isle/agent/FileCheck.scala new file mode 100644 index 0000000..31cc212 --- /dev/null +++ b/src/main/scala/io/datastrophic/isle/agent/FileCheck.scala @@ -0,0 +1,13 @@ +package io.datastrophic.isle.agent + +import java.io.File + +class FileCheck(path: String) { + def checkFile() = { + if(new File(path).exists()){ + println("[healthy]") + } else { + println(s"[unhealthy] file not found: $path") + } + } +} diff --git a/src/main/scala/io/datastrophic/isle/agent/HeartBeatCheck.scala b/src/main/scala/io/datastrophic/isle/agent/HeartBeatCheck.scala new file mode 100644 index 0000000..e3013d6 --- /dev/null +++ b/src/main/scala/io/datastrophic/isle/agent/HeartBeatCheck.scala @@ -0,0 +1,7 @@ +package io.datastrophic.isle.agent + +class HeartBeatCheck { + def heartbeat() = { + println("[healthy]") + } +}