Use IO monad when starting servers 59/58659/1
authorPiotr Jaszczyk <piotr.jaszczyk@nokia.com>
Wed, 4 Jul 2018 11:16:21 +0000 (13:16 +0200)
committerPiotr Jaszczyk <piotr.jaszczyk@nokia.com>
Thu, 2 Aug 2018 10:01:26 +0000 (12:01 +0200)
Change-Id: I3e97161535fc721dda6109c4cb5f23a1db0afde3
Signed-off-by: Piotr Jaszczyk <piotr.jaszczyk@nokia.com>
Issue-ID: DCAEGEN2-601

12 files changed:
hv-collector-client-simulator/src/main/kotlin/org/onap/dcae/collectors/veshv/simulators/xnf/main.kt
hv-collector-client-simulator/src/test/kotlin/org/onap/dcae/collectors/veshv/main/config/ArgBasedClientConfigurationTest.kt
hv-collector-dcae-app-simulator/src/main/kotlin/org/onap/dcae/collectors/veshv/simulators/dcaeapp/main.kt
hv-collector-dcae-app-simulator/src/test/kotlin/org/onap/dcae/collectors/veshv/simulators/dcaeapp/config/ArgBasedDcaeAppSimConfigurationTest.kt
hv-collector-main/src/main/kotlin/org/onap/dcae/collectors/veshv/main/main.kt
hv-collector-main/src/test/kotlin/org/onap/dcae/collectors/veshv/main/ArgBasedServerConfigurationTest.kt
hv-collector-utils/pom.xml
hv-collector-utils/src/main/kotlin/org/onap/dcae/collectors/veshv/utils/arrow/core.kt [new file with mode: 0644]
hv-collector-utils/src/main/kotlin/org/onap/dcae/collectors/veshv/utils/arrow/effects.kt [new file with mode: 0644]
hv-collector-utils/src/main/kotlin/org/onap/dcae/collectors/veshv/utils/commandline/ArgBasedConfiguration.kt
hv-collector-utils/src/main/kotlin/org/onap/dcae/collectors/veshv/utils/commandline/WrongArgumentError.kt [moved from hv-collector-utils/src/main/kotlin/org/onap/dcae/collectors/veshv/utils/commandline/WrongArgumentException.kt with 92% similarity]
hv-collector-utils/src/main/kotlin/org/onap/dcae/collectors/veshv/utils/commandline/extensions.kt

index dbeba2b..63c4875 100644 (file)
  */
 package org.onap.dcae.collectors.veshv.simulators.xnf
 
-import arrow.core.Failure
-import arrow.core.Success
 import org.onap.dcae.collectors.veshv.simulators.xnf.config.ArgBasedClientConfiguration
 import org.onap.dcae.collectors.veshv.simulators.xnf.impl.HttpServer
 import org.onap.dcae.collectors.veshv.simulators.xnf.impl.VesHvClient
-import org.onap.dcae.collectors.veshv.utils.commandline.handleErrorsInMain
+import org.onap.dcae.collectors.veshv.utils.arrow.ExitFailure
+import org.onap.dcae.collectors.veshv.utils.arrow.unsafeRunEitherSync
+import org.onap.dcae.collectors.veshv.utils.arrow.void
+import org.onap.dcae.collectors.veshv.utils.commandline.handleWrongArgumentErrorCurried
 import org.onap.dcae.collectors.veshv.utils.logging.Logger
+import org.slf4j.LoggerFactory
 
-
-private val logger = Logger("Simulator :: main")
-private const val PROGRAM_NAME = "java org.onap.dcae.collectors.veshv.main.MainKt"
+private const val PACKAGE_NAME = "org.onap.dcae.collectors.veshv.simulators.xnf"
+private val logger = Logger(PACKAGE_NAME)
+const val PROGRAM_NAME = "java $PACKAGE_NAME.MainKt"
 
 /**
  * @author Jakub Dudycz <jakub.dudycz@nokia.com>
  * @since June 2018
  */
-fun main(args: Array<String>) {
-    val httpServer = ArgBasedClientConfiguration().parse(args)
+fun main(args: Array<String>) =
+    ArgBasedClientConfiguration().parse(args)
+            .mapLeft(handleWrongArgumentErrorCurried(PROGRAM_NAME))
             .map(::VesHvClient)
             .map(::HttpServer)
-
-    when (httpServer) {
-        is Success -> httpServer.value.start().unsafeRunAsync {
-            it.fold(
+            .map { it.start().void() }
+            .unsafeRunEitherSync(
                     { ex ->
                         logger.error("Failed to start a server", ex)
+                        ExitFailure(1)
                     },
-                    { srv ->
-                        logger.info("Started Simulator API server (listening on ${srv.bindHost}:${srv.bindPort})")
+                    {
+                        logger.info("Started xNF Simulator API server")
                     }
             )
-        }
-        is Failure -> httpServer.handleErrorsInMain(PROGRAM_NAME, logger)
-    }
-}
index 2746c0a..3b1836e 100644 (file)
@@ -21,6 +21,7 @@ package org.onap.dcae.collectors.veshv.main.config
 
 import arrow.core.Failure
 import arrow.core.Success
+import arrow.core.identity
 import org.assertj.core.api.Assertions.assertThat
 import org.jetbrains.spek.api.Spek
 import org.jetbrains.spek.api.dsl.describe
@@ -46,13 +47,11 @@ object ArgBasedClientConfigurationTest : Spek({
         cut = ArgBasedClientConfiguration()
     }
 
-    fun parse(vararg cmdLine: String): ClientConfiguration {
-        val result = cut.parse(cmdLine)
-        return when (result) {
-            is Success -> result.value
-            is Failure -> throw AssertionError("Parsing result should be present")
-        }
-    }
+    fun parse(vararg cmdLine: String): ClientConfiguration =
+            cut.parse(cmdLine).fold(
+                    {throw AssertionError("Parsing result should be present")},
+                    ::identity
+            )
 
     describe("parsing arguments") {
         lateinit var result: ClientConfiguration
index f7d44ed..a8a4cf5 100644 (file)
  */
 package org.onap.dcae.collectors.veshv.simulators.dcaeapp
 
-import arrow.core.Failure
-import arrow.core.Success
 import org.onap.dcae.collectors.veshv.simulators.dcaeapp.config.ArgBasedDcaeAppSimConfiguration
 import org.onap.dcae.collectors.veshv.simulators.dcaeapp.config.DcaeAppSimConfiguration
 import org.onap.dcae.collectors.veshv.simulators.dcaeapp.kafka.KafkaSource
 import org.onap.dcae.collectors.veshv.simulators.dcaeapp.remote.ApiServer
-import org.onap.dcae.collectors.veshv.utils.commandline.handleErrorsInMain
+import org.onap.dcae.collectors.veshv.utils.arrow.ExitFailure
+import org.onap.dcae.collectors.veshv.utils.arrow.unsafeRunEitherSync
+import org.onap.dcae.collectors.veshv.utils.arrow.void
+import org.onap.dcae.collectors.veshv.utils.commandline.handleWrongArgumentErrorCurried
 import org.onap.dcae.collectors.veshv.utils.logging.Logger
 import org.slf4j.LoggerFactory
 
-private val logger = Logger(LoggerFactory.getLogger("DCAE simulator :: main"))
+private const val PACKAGE_NAME = "org.onap.dcae.collectors.veshv.simulators.dcaeapp"
+private val logger = Logger(PACKAGE_NAME)
+const val PROGRAM_NAME = "java $PACKAGE_NAME.MainKt"
 
-fun main(args: Array<String>) {
-    logger.info("Starting DCAE APP simulator")
+fun main(args: Array<String>) =
+        ArgBasedDcaeAppSimConfiguration().parse(args)
+                .mapLeft(handleWrongArgumentErrorCurried(PROGRAM_NAME))
+                .map(::startApp)
+                .unsafeRunEitherSync(
+                        { ex ->
+                            logger.error("Failed to start a server", ex)
+                            ExitFailure(1)
+                        },
+                        {
+                            logger.info("Started DCAE-APP Simulator API server")
+                        }
+                )
 
-    val config = ArgBasedDcaeAppSimConfiguration().parse(args)
-    when (config) {
-        is Success -> startApp(config.value).unsafeRunSync()
-        is Failure -> config.handleErrorsInMain("", logger)
-    }
-}
 
 private fun startApp(config: DcaeAppSimConfiguration) =
         KafkaSource.create(config.kafkaBootstrapServers, config.kafkaTopics)
                 .start()
                 .map(::ApiServer)
-                .flatMap { it.start(config.apiPort) }
+                .flatMap { it.start(config.apiPort).void() }
index d99de17..b73a788 100644 (file)
@@ -21,12 +21,13 @@ package org.onap.dcae.collectors.veshv.simulators.dcaeapp.config
 
 import arrow.core.Failure
 import arrow.core.Success
+import arrow.core.identity
 import org.assertj.core.api.Assertions.assertThat
 import org.jetbrains.spek.api.Spek
 import org.jetbrains.spek.api.dsl.describe
 import org.jetbrains.spek.api.dsl.given
 import org.jetbrains.spek.api.dsl.it
-import org.onap.dcae.collectors.veshv.utils.commandline.WrongArgumentException
+import org.onap.dcae.collectors.veshv.utils.commandline.WrongArgumentError
 
 
 internal class ArgBasedDcaeAppSimConfigurationTest : Spek({
@@ -39,21 +40,17 @@ internal class ArgBasedDcaeAppSimConfigurationTest : Spek({
         cut = ArgBasedDcaeAppSimConfiguration()
     }
 
-    fun parseExpectingSuccess(vararg cmdLine: String): DcaeAppSimConfiguration {
-        val result = cut.parse(cmdLine)
-        return when (result) {
-            is Success -> result.value
-            is Failure -> throw AssertionError("Parsing result should be present")
-        }
-    }
+    fun parseExpectingSuccess(vararg cmdLine: String): DcaeAppSimConfiguration =
+            cut.parse(cmdLine).fold(
+                    { throw AssertionError("Parsing result should be present") },
+                    ::identity
+            )
 
-    fun parseExpectingFailure(vararg cmdLine: String): Throwable {
-        val result = cut.parse(cmdLine)
-        return when (result) {
-            is Success -> throw AssertionError("parsing should have failed")
-            is Failure -> result.exception
-        }
-    }
+    fun parseExpectingFailure(vararg cmdLine: String) =
+            cut.parse(cmdLine).fold(
+                    ::identity,
+                    { throw AssertionError("parsing should have failed") }
+            )
 
     describe("parsing arguments") {
         lateinit var result: DcaeAppSimConfiguration
@@ -121,14 +118,14 @@ internal class ArgBasedDcaeAppSimConfigurationTest : Spek({
             given("kafka topics are missing") {
                 it("should throw exception") {
                     assertThat(parseExpectingFailure("-s", kafkaBootstrapServers))
-                            .isInstanceOf(WrongArgumentException::class.java)
+                            .isInstanceOf(WrongArgumentError::class.java)
                 }
             }
 
             given("kafka bootstrap servers are missing") {
                 it("should throw exception") {
                     assertThat(parseExpectingFailure("-f", kafkaTopics))
-                            .isInstanceOf(WrongArgumentException::class.java)
+                            .isInstanceOf(WrongArgumentError::class.java)
                 }
             }
         }
index d1c3b4a..f5efab2 100644 (file)
  */
 package org.onap.dcae.collectors.veshv.main
 
-import arrow.core.flatMap
 import org.onap.dcae.collectors.veshv.boundary.Server
+import org.onap.dcae.collectors.veshv.boundary.ServerHandle
 import org.onap.dcae.collectors.veshv.factory.CollectorFactory
 import org.onap.dcae.collectors.veshv.factory.ServerFactory
 import org.onap.dcae.collectors.veshv.impl.adapters.AdapterFactory
 import org.onap.dcae.collectors.veshv.model.ServerConfiguration
-import org.onap.dcae.collectors.veshv.utils.commandline.handleErrorsInMain
+import org.onap.dcae.collectors.veshv.utils.arrow.ExitFailure
+import org.onap.dcae.collectors.veshv.utils.arrow.unsafeRunEitherSync
+import org.onap.dcae.collectors.veshv.utils.arrow.void
+import org.onap.dcae.collectors.veshv.utils.commandline.handleWrongArgumentErrorCurried
 import org.onap.dcae.collectors.veshv.utils.logging.Logger
 
 private val logger = Logger("org.onap.dcae.collectors.veshv.main")
 private const val PROGRAM_NAME = "java org.onap.dcae.collectors.veshv.main.MainKt"
 
-fun main(args: Array<String>) {
-    ArgBasedServerConfiguration().parse(args)
-            .toEither()
-            .map(::createServer)
-            .map(Server::start)
-            .flatMap { it.attempt().unsafeRunSync() }
-            .fold(
-                    { ex ->
-                        handleErrorsInMain(ex, PROGRAM_NAME, logger)
-                    },
-                    { handle ->
-                        logger.info("Server started. Listening on ${handle.host}:${handle.port}")
-                        handle.await().unsafeRunSync()
-                    }
-            )
-}
+fun main(args: Array<String>) =
+        ArgBasedServerConfiguration().parse(args)
+                .mapLeft(handleWrongArgumentErrorCurried(PROGRAM_NAME))
+                .map(::createServer)
+                .map {
+                    it.start()
+                            .map(::logServerStarted)
+                            .flatMap(ServerHandle::await)
+                }
+                .unsafeRunEitherSync(
+                        { ex ->
+                            logger.error("Failed to start a server", ex)
+                            ExitFailure(1)
+                        },
+                        { logger.info("Gentle shutdown") }
+                )
+
 
 private fun createServer(config: ServerConfiguration): Server {
     val sink = if (config.dummyMode) AdapterFactory.loggingSink() else AdapterFactory.kafkaSink()
@@ -60,3 +64,7 @@ private fun createServer(config: ServerConfiguration): Server {
     return ServerFactory.createNettyTcpServer(config, collectorProvider)
 }
 
+private fun logServerStarted(handle: ServerHandle): ServerHandle {
+    logger.info("HighVolume VES Collector is up and listening on ${handle.host}:${handle.port}")
+    return handle
+}
index a14801d..2c49cf9 100644 (file)
@@ -21,6 +21,7 @@ package org.onap.dcae.collectors.veshv.main
 
 import arrow.core.Failure
 import arrow.core.Success
+import arrow.core.identity
 import org.assertj.core.api.Assertions.assertThat
 import org.jetbrains.spek.api.Spek
 import org.jetbrains.spek.api.dsl.describe
@@ -49,13 +50,11 @@ object ArgBasedServerConfigurationTest : Spek({
         cut = ArgBasedServerConfiguration()
     }
 
-    fun parse(vararg cmdLine: String): ServerConfiguration {
-        val result = cut.parse(cmdLine)
-        return when (result) {
-            is Success -> result.value
-            is Failure -> throw AssertionError("Parsing result should be present")
-        }
-    }
+    fun parse(vararg cmdLine: String): ServerConfiguration =
+            cut.parse(cmdLine).fold(
+                    {throw AssertionError("Parsing result should be present")},
+                    ::identity
+            )
 
     describe("parsing arguments") {
         given("all parameters are present in the long form") {
index 3c48280..ea19ba3 100644 (file)
             <groupId>io.arrow-kt</groupId>
             <artifactId>arrow-instances-data</artifactId>
         </dependency>
+        <dependency>
+            <groupId>io.arrow-kt</groupId>
+            <artifactId>arrow-effects</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>io.arrow-kt</groupId>
+            <artifactId>arrow-syntax</artifactId>
+        </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
diff --git a/hv-collector-utils/src/main/kotlin/org/onap/dcae/collectors/veshv/utils/arrow/core.kt b/hv-collector-utils/src/main/kotlin/org/onap/dcae/collectors/veshv/utils/arrow/core.kt
new file mode 100644 (file)
index 0000000..39964c1
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * ============LICENSE_START=======================================================
+ * dcaegen2-collectors-veshv
+ * ================================================================================
+ * Copyright (C) 2018 NOKIA
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.dcae.collectors.veshv.utils.arrow
+
+import arrow.core.Either
+import arrow.core.identity
+
+/**
+ * @author Piotr Jaszczyk <piotr.jaszczyk@nokia.com>
+ * @since July 2018
+ */
+
+
+fun <A> Either<A, A>.flatten() = fold(::identity, ::identity)
diff --git a/hv-collector-utils/src/main/kotlin/org/onap/dcae/collectors/veshv/utils/arrow/effects.kt b/hv-collector-utils/src/main/kotlin/org/onap/dcae/collectors/veshv/utils/arrow/effects.kt
new file mode 100644 (file)
index 0000000..e37b0d7
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * ============LICENSE_START=======================================================
+ * dcaegen2-collectors-veshv
+ * ================================================================================
+ * Copyright (C) 2018 NOKIA
+ * ================================================================================
+ * 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.
+ * ============LICENSE_END=========================================================
+ */
+package org.onap.dcae.collectors.veshv.utils.arrow
+
+import arrow.core.Either
+import arrow.effects.IO
+import kotlin.system.exitProcess
+
+/**
+ * @author Piotr Jaszczyk <piotr.jaszczyk@nokia.com>
+ * @since June 2018
+ */
+
+sealed class ExitCode {
+    abstract val code: Int
+
+    fun io() = IO {
+        exitProcess(code)
+    }
+}
+
+object ExitSuccess : ExitCode() {
+    override val code = 0
+}
+
+data class ExitFailure(override val code: Int) : ExitCode()
+
+fun Either<IO<Unit>, IO<Unit>>.unsafeRunEitherSync(onError: (Throwable) -> ExitCode, onSuccess: () -> Unit) =
+        flatten().attempt().unsafeRunSync().fold({ onError(it).io().unsafeRunSync() }, { onSuccess() })
+
+
+fun IO<Any>.void() = map { Unit }
index 34c0e65..d5855ca 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.onap.dcae.collectors.veshv.utils.commandline
 
+import arrow.core.Either
 import arrow.core.Option
 import arrow.core.Try
 import arrow.core.getOrElse
@@ -30,16 +31,18 @@ import java.io.File
 import java.nio.file.Path
 import java.nio.file.Paths
 
-abstract class ArgBasedConfiguration<T>(val parser: CommandLineParser) {
+abstract class ArgBasedConfiguration<T>(private val parser: CommandLineParser) {
     abstract val cmdLineOptionsList: List<CommandLineOption>
 
-    fun parse(args: Array<out String>): Try<T> {
+    fun parse(args: Array<out String>): Either<WrongArgumentError, T> {
         val commandLineOptions = cmdLineOptionsList.map { it.option }.fold(Options(), Options::addOption)
-        return Try {
+        val parseResult = Try {
             parser.parse(commandLineOptions, args)
-        }.recoverWith { ex ->
-            Try.raise<CommandLine>(WrongArgumentException(ex, commandLineOptions))
-        }.map (this::getConfiguration)
+        }
+        return parseResult
+                .toEither()
+                .mapLeft { ex -> WrongArgumentError(ex, commandLineOptions) }
+                .map(this::getConfiguration)
     }
 
     protected abstract fun getConfiguration(cmdLine: CommandLine): T
@@ -23,11 +23,10 @@ import org.apache.commons.cli.HelpFormatter
 import org.apache.commons.cli.Options
 
 
-class WrongArgumentException(
-        message: String,
+data class WrongArgumentError(
+        val message: String,
         private val options: Options,
-        parent: Throwable? = null
-) : Exception(message, parent) {
+        val cause: Throwable? = null) {
 
     constructor(par: Throwable, options: Options) : this(par.message ?: "", options, par)
 
index 23bf165..718ebf8 100644 (file)
  */
 package org.onap.dcae.collectors.veshv.utils.commandline
 
-import arrow.core.Failure
-import org.onap.dcae.collectors.veshv.utils.logging.Logger
-import kotlin.system.exitProcess
+import arrow.effects.IO
+import arrow.syntax.function.curried
+import org.onap.dcae.collectors.veshv.utils.arrow.ExitFailure
 
 /**
  * @author Piotr Jaszczyk <piotr.jaszczyk@nokia.com>
  * @since June 2018
  */
 
-fun handleErrorsInMain(ex: Throwable, programName: String, logger: Logger) {
-    when (ex) {
-        is WrongArgumentException -> {
-            ex.printMessage()
-            ex.printHelp(programName)
-            exitProcess(1)
-        }
+fun handleWrongArgumentError(programName: String, err: WrongArgumentError): IO<Unit> = IO {
+    err.printMessage()
+    err.printHelp(programName)
+}.flatMap { ExitFailure(2).io() }
 
-        else -> {
-            logger.error(ex.localizedMessage)
-            logger.debug("An error occurred when starting VES HV Collector", ex)
-            System.exit(2)
-        }
-    }
-}
-
-
-fun <A> Failure<A>.handleErrorsInMain(programName: String, logger: Logger) {
-    handleErrorsInMain(this.exception, programName, logger)
-}
+val handleWrongArgumentErrorCurried = ::handleWrongArgumentError.curried()