From 83836d13f61099d1da9806854df7900204199975 Mon Sep 17 00:00:00 2001 From: Override-6 Date: Fri, 25 Nov 2022 16:54:11 +0100 Subject: [PATCH] Increment now working, tests redacted, Currently configuring CI/CD --- .drone.yml | 31 +++++++++++++ .gitignore | 1 + .../main/scala/org/tbasket/api/Endpoint.scala | 44 ++++++++++++++----- .../api/compute/APIRequestHandler.scala | 7 +-- build.gradle | 22 ++++++++-- drone/deliver.sh | 12 +++++ drone/deploy.sh | 19 ++++++++ src/main/resources/log4j2.xml | 40 +++++++++++++++++ .../scala/org/tbasket/EndpointSetup.scala | 13 +++--- src/main/scala/org/tbasket/Main.scala | 7 +++ .../tbasket/compute/IncrementHandler.scala | 24 +++++----- .../test/IncrementRequestHandler.scala | 39 ++++++++++++++++ 12 files changed, 222 insertions(+), 37 deletions(-) create mode 100644 .drone.yml create mode 100644 drone/deliver.sh create mode 100644 drone/deploy.sh create mode 100644 src/main/resources/log4j2.xml create mode 100644 src/test/scala/org/tbasket/test/IncrementRequestHandler.scala diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..80d7887 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,31 @@ +kind: pipeline +type: docker +name: Deployment + +trigger: + branch: + - production + - dev + +steps: + - name: 'Unit Tests' + image: ubuntu:latest + commands: + - apt update && apt install openjdk-11-jdk -y + - ./gradlew :test + + - name: deploy to server + image: ubuntu:latest + depends_on: + - 'Unit Tests' + environment: + SSH_PRIVATE_KEY: + from_secret: ??? + SSH_PUBLIC_KEY: + from_secret: ??? + USER: + from_secret: ??? + IP: + from_secret: ??? + commands: + - drone/deliver.sh $DRONE_BRANCH diff --git a/.gitignore b/.gitignore index dfa9b60..13e1c73 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ build .gradle .idea */.gradle/ +/log \ No newline at end of file diff --git a/API/src/main/scala/org/tbasket/api/Endpoint.scala b/API/src/main/scala/org/tbasket/api/Endpoint.scala index 09b81b4..be27204 100644 --- a/API/src/main/scala/org/tbasket/api/Endpoint.scala +++ b/API/src/main/scala/org/tbasket/api/Endpoint.scala @@ -1,36 +1,58 @@ package org.tbasket.api +import org.apache.logging.log4j.LogManager +import org.tbasket.api.Endpoint.LOG import org.tbasket.api.compute.APIRequestHandler import zio._ import zio.http.ServerConfig.LeakDetectionLevel import zio.http._ +import zio.http.model.Method.{GET, POST} +import zio.http.model.Status import scala.collection.mutable -class Endpoint(hostname: String, port: Int, rootPath: Option[String]) extends ZIOAppDefault { +class Endpoint(hostname: String, port: Int) extends ZIOAppDefault { - private val Root: Path = rootPath.map(Path.decode).getOrElse(Path.root) - private val handlers = mutable.HashMap.empty[String, APIRequestHandler] + private val handlers = mutable.HashMap.empty[String, APIRequestHandler] def bind(path: String)(handler: APIRequestHandler): Unit = { handlers.put(path, handler) } - private val app = Http.collectZIO[Request] { - case r@_ -> Root / path if handlers.contains(path) => - handlers(path).handle.provideEnvironment(ZEnvironment(r)) - case _ -> path => - ZIO.fail(new APIException(s"Unable to find a handler for page '$path'")) + //set generic required headers + private def transform(response: Response): Response = { + response.withAccessControlAllowOrigin("*") } + + + private val app = Http.collect[Request] { + case r@GET -> _ / path if handlers.contains(path) => + transform(handlers(path).get(r)) + case r@POST -> _ / path if handlers.contains(path) => + transform(handlers(path).post(r)) + case r@method -> path => + val ipInsights = r.remoteAddress + .map(ip => s": request received from $ip.") + .getOrElse("") + LOG.error(s"Was unable to find a handler for request '$path' with method $method ${ipInsights}") + Response(Status.NotFound) + } + val run = { val config = ServerConfig.default .port(port) .leakDetection(LeakDetectionLevel.PARANOID) val configLayer = ServerConfig.live(config) - (Server.install(app).flatMap { port => - Console.printLine(s"Listening API entries on $hostname:$port$rootPath") - } *> ZIO.never).provide(configLayer, Server.live) + Server.install(app).flatMap { port => + LOG.info(s"Listening API entries on $hostname:$port") + ZIO.never + }.provide(configLayer, Server.live) } } + +object Endpoint { + final val LOG = LogManager.getLogger("API") + +} diff --git a/API/src/main/scala/org/tbasket/api/compute/APIRequestHandler.scala b/API/src/main/scala/org/tbasket/api/compute/APIRequestHandler.scala index b9334c9..a0807d4 100644 --- a/API/src/main/scala/org/tbasket/api/compute/APIRequestHandler.scala +++ b/API/src/main/scala/org/tbasket/api/compute/APIRequestHandler.scala @@ -1,11 +1,12 @@ package org.tbasket.api.compute -import org.tbasket.api.APIException -import zio.ZIO +import zio.http.model.Status import zio.http.{Request, Response} trait APIRequestHandler { - def handle: ZIO[Request, APIException, Response] + def get(request: Request): Response = Response(Status.MethodNotAllowed) + + def post(request: Request): Response = Response(Status.MethodNotAllowed) } diff --git a/build.gradle b/build.gradle index 8a40242..d9bbb9b 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,9 @@ plugins { id 'java' id 'java-library' id 'scala' - id 'application' + id 'application' //for 'run' task + id 'com.adarshr.test-logger' version '3.2.0' //fancy prints during tests + id 'com.github.johnrengelman.shadow' version '7.1.2' //for optimised jar } final var scalaVersion = "2.13" @@ -15,15 +17,27 @@ repositories { mavenCentral() } +shadowJar { + archivesBaseName = "server" +} dependencies { implementation project(':API') implementation project(':DB') - implementation "io.circe:circe-core_$scalaVersion:0.15.0-M1" + testImplementation "io.circe:circe-core_$scalaVersion:0.15.0-M1" + testImplementation "io.circe:circe-parser_$scalaVersion:0.15.0-M1" + +} + +testlogger { + theme 'mocha' } test { useJUnitPlatform() + testlogger { + theme 'standard-parallel' + } } run { @@ -33,7 +47,6 @@ run { allprojects { apply plugin: 'scala' apply plugin: 'java-library' - version '1.0-SNAPSHOT' repositories { mavenCentral() @@ -43,6 +56,9 @@ allprojects { testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1' testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1' + implementation 'org.apache.logging.log4j:log4j-core:2.17.2' + implementation 'org.apache.logging.log4j:log4j-api:2.17.2' + implementation 'org.slf4j:slf4j-simple:2.0.4' implementation "org.scala-lang:scala-library:$scalaVersion.10" } diff --git a/drone/deliver.sh b/drone/deliver.sh new file mode 100644 index 0000000..77c9b82 --- /dev/null +++ b/drone/deliver.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +apt update && apt install openjdk-11-jdk sftp -y + +PATH="$PATH:." + +gradlew :shadowJar + +mkdir -p ~/.ssh +echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa +echo "$SSH_PUBLIC_KEY" > ~/.ssh/id_rsa.pub +sftp "$USER@$IP:tbasket/" <<< $'put build/libs/server-all.jar drone/deploy.sh' \ No newline at end of file diff --git a/drone/deploy.sh b/drone/deploy.sh new file mode 100644 index 0000000..f1e2b5f --- /dev/null +++ b/drone/deploy.sh @@ -0,0 +1,19 @@ + + +SERVER_JAR_NAME="server-all.jar" + +OLD_PID=$(ps -aux | grep "-jar $SERVER_JAR_NAME" | tr -s " " | cut -d " " -f2) + +#if $OLD_PID is not empty but isn't a number, something went wrong +if [ "$OLD_PID" ] && ! grep -E -q "^[0-9]+$"; then + echo "error, unable to retrieve old server pid: $OLD_PID" >&2 + exit 2 +fi + + +if [ "$OLD_PID" ]; then + #will cause the old server to gracefully shutdown + echo "shutting down old server version ..." + kill SIGQUIT "$OLD_PID" + wait "$OLD_PID" +fi diff --git a/src/main/resources/log4j2.xml b/src/main/resources/log4j2.xml new file mode 100644 index 0000000..5abd931 --- /dev/null +++ b/src/main/resources/log4j2.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/scala/org/tbasket/EndpointSetup.scala b/src/main/scala/org/tbasket/EndpointSetup.scala index 1e22d7f..578cd5d 100644 --- a/src/main/scala/org/tbasket/EndpointSetup.scala +++ b/src/main/scala/org/tbasket/EndpointSetup.scala @@ -7,11 +7,11 @@ import java.util.Properties object EndpointSetup { - final val EndpointUrl = "endpoint.url" + final val EndpointUrl = "endpoint.url" final val EndpointUrlDefault = s"localhost:48485" def setupEndpoint(): Endpoint = { - println("Initializing API endpoint...") + Main.LOG.debug("Initializing API endpoint...") val endpoint = createEndpoint() endpoint.bind("counter")(IncrementHandler) endpoint @@ -21,13 +21,12 @@ object EndpointSetup { val properties = new Properties() val in = getClass.getClassLoader.getResourceAsStream("server.properties") properties.load(in) - val (hostname, port, endpointPath) = properties + val (hostname, port) = properties .getProperty(EndpointUrl, EndpointUrlDefault) match { - case s"$ip:$port/$endpointPath" => (ip, port.toInt, Some(endpointPath)) - case s"$ip:$port" => (ip, port.toInt, None) - case v => throw new InternalBasketServerException(s"$EndpointUrl property value is wrong: $v must be :/[endpointPath]") + case s"$ip:$port" => (ip, port.toInt) + case v => throw new InternalBasketServerException(s"$EndpointUrl property value is wrong: $v must be :/[endpointPath]") } - new Endpoint(hostname, port, endpointPath) + new Endpoint(hostname, port) } } diff --git a/src/main/scala/org/tbasket/Main.scala b/src/main/scala/org/tbasket/Main.scala index 52e14ae..6977068 100644 --- a/src/main/scala/org/tbasket/Main.scala +++ b/src/main/scala/org/tbasket/Main.scala @@ -1,14 +1,18 @@ package org.tbasket +import org.apache.logging.log4j.LogManager import zio._ +import java.lang import scala.io.StdIn import scala.util.control.NonFatal object Main { + final val LOG = LogManager.getLogger("Core") def main(args: Array[String]): Unit = { + LOG.info("Starting server") val endpoint = EndpointSetup.setupEndpoint() val runtime = Runtime.default Unsafe.unsafe { implicit u => @@ -18,9 +22,12 @@ object Main { throw e } } + LOG.info("Server successfully started") println("enter to exit") StdIn.readLine() } + //add a shutdown hook to log when the server is about to get killed + lang.Runtime.getRuntime.addShutdownHook(new Thread(() => LOG.info("Server shutdowns"))) } diff --git a/src/main/scala/org/tbasket/compute/IncrementHandler.scala b/src/main/scala/org/tbasket/compute/IncrementHandler.scala index fe78c0d..e8aed70 100644 --- a/src/main/scala/org/tbasket/compute/IncrementHandler.scala +++ b/src/main/scala/org/tbasket/compute/IncrementHandler.scala @@ -1,23 +1,21 @@ package org.tbasket.compute -import org.tbasket.api.APIException import org.tbasket.api.compute.APIRequestHandler -import zio.ZIO -import zio.http.model.Method._ import zio.http.{Request, Response} + object IncrementHandler extends APIRequestHandler { - private var i = 0 + @volatile private var i = 0 + + def getCounter: Int = i + override def get(request: Request): Response = { + Response.json(s"{\"value\": $i}") + } - override def handle: ZIO[Request, APIException, Response] = {r: Request => - r match { - case Request(_, _, GET, _, _, _) => - ZIO.succeed(Response.json(s"value: $i")) - case Request(_, _, POST, _, _, _) => - i += 1 - ZIO.succeed(Response.ok) - Console - } + override def post(request: Request): Response = { + i += 1 + println(s"Counter is now $i") + Response.ok } } diff --git a/src/test/scala/org/tbasket/test/IncrementRequestHandler.scala b/src/test/scala/org/tbasket/test/IncrementRequestHandler.scala new file mode 100644 index 0000000..17f0aac --- /dev/null +++ b/src/test/scala/org/tbasket/test/IncrementRequestHandler.scala @@ -0,0 +1,39 @@ +package org.tbasket.test + +import io.circe.parser._ +import org.junit.jupiter.api.{Assertions, Test} +import org.tbasket.compute.IncrementHandler +import zio.http.model.Status +import zio.http.{Body, Path, Request, URL} + +class IncrementRequestHandler { + + private val url = URL(Path.decode("counter")) + + @Test + def testMakeIncrement(): Unit = { + val last = IncrementHandler.getCounter + val response = IncrementHandler.post(Request.post(Body.empty, url)) + Assertions.assertEquals(response.status, Status.Ok) + Assertions.assertEquals(last + 1, IncrementHandler.getCounter) + } + + @Test + def testGetIncrement(): Unit = { + val counter = IncrementHandler.getCounter + val response = IncrementHandler.get(Request.post(Body.empty, url)) + for { + json <- response.body.asString + } yield parse(json) match { + case Left(failure) => Assertions.fail(s"returned json is invalid ($failure)") + case Right(json) => + val valueJsons = json.findAllByKey("value") + Assertions.assertEquals(1, valueJsons.size) + val valueCounter = valueJsons.head.asNumber.get.toInt.get + Assertions.assertEquals(counter, valueCounter) + } + Assertions.assertEquals(response.status, Status.Ok) + Assertions.assertEquals(counter, IncrementHandler.getCounter) + } + +}