From 244150d6580520c5064f38860c04630028be57ef Mon Sep 17 00:00:00 2001 From: Matthew Nesbit Date: Tue, 2 Aug 2016 18:15:11 +0100 Subject: [PATCH] Standalone node built into one simple jar and reads config from .conf file. --- build.gradle | 74 ++++---- config/dev/generalnode.conf | 13 ++ config/dev/nameservernode.conf | 13 ++ .../kotlin/com/r3corda/standalone/Main.kt | 166 ++++++++++++++++++ 4 files changed, 229 insertions(+), 37 deletions(-) create mode 100644 config/dev/generalnode.conf create mode 100644 config/dev/nameservernode.conf create mode 100644 src/main/kotlin/com/r3corda/standalone/Main.kt diff --git a/build.gradle b/build.gradle index 907c07eee0..b382816ffe 100644 --- a/build.gradle +++ b/build.gradle @@ -1,40 +1,3 @@ -group 'com.r3corda' - -apply plugin: 'kotlin' -apply plugin: 'application' -apply plugin: 'project-report' -apply plugin: QuasarPlugin -apply plugin: 'com.github.ben-manes.versions' - -allprojects { - apply plugin: 'java' - apply plugin: 'jacoco' - - sourceCompatibility = 1.8 - targetCompatibility = 1.8 - - tasks.withType(JavaCompile) { - options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" - } - - // Our version: bump this on release. - group 'com.r3corda' - version '0.3-SNAPSHOT' -} - -subprojects { - jacocoTestReport { - additionalSourceDirs = files(sourceSets.main.allSource.srcDirs) - sourceDirectories = files(sourceSets.main.allSource.srcDirs) - classDirectories = files(sourceSets.main.output) - reports { - html.enabled = true - xml.enabled = true - csv.enabled = false - } - } -} - buildscript { ext.kotlin_version = '1.0.3' ext.quasar_version = '0.7.5' @@ -60,6 +23,32 @@ buildscript { } } +plugins { + id "us.kirchmeier.capsule" version "1.0.2" +} + +apply plugin: 'kotlin' +apply plugin: 'application' +apply plugin: 'project-report' +apply plugin: QuasarPlugin +apply plugin: 'com.github.ben-manes.versions' + +allprojects { + apply plugin: 'java' + apply plugin: 'jacoco' + + sourceCompatibility = 1.8 + targetCompatibility = 1.8 + + tasks.withType(JavaCompile) { + options.compilerArgs << "-Xlint:unchecked" << "-Xlint:deprecation" + } + + // Our version: bump this on release. + group 'com.r3corda' + version '0.2-SNAPSHOT' +} + repositories { mavenLocal() mavenCentral() @@ -200,4 +189,15 @@ applicationDistribution.into("bin") { from(getIRSDemo) from(getTraderDemo) fileMode = 0755 +} + +task createCapsule(type:FatCapsule, dependsOn: 'quasarScan') { + applicationClass 'com.r3corda.standalone.MainKt' + + capsuleManifest { + appClassPath = ["jolokia-agent-war-${project.ext.jolokia_version}.war"] + systemProperties['log4j.configuration'] = 'log4j2.xml' + javaAgents = ["quasar-core-${quasar_version}-jdk8.jar"] + minJavaVersion = '1.8.0' + } } \ No newline at end of file diff --git a/config/dev/generalnode.conf b/config/dev/generalnode.conf new file mode 100644 index 0000000000..9731aa6c7b --- /dev/null +++ b/config/dev/generalnode.conf @@ -0,0 +1,13 @@ +basedir : "./standalone/node1", +myLegalName : "Node 1", +nearestCity : "London", +keyStorePassword : "cordacadevpass", +trustStorePassword : "trustpass", +artemisAddress : "localhost:31337", +webAddress : "localhost:31338", +hostNotaryServiceLocally: false, +mapService : { + hostServiceLocally : false, + address : "localhost:12345", + identity : "Corda Name Service" +} diff --git a/config/dev/nameservernode.conf b/config/dev/nameservernode.conf new file mode 100644 index 0000000000..bfebc90d0e --- /dev/null +++ b/config/dev/nameservernode.conf @@ -0,0 +1,13 @@ +basedir : "./standalone/nameserver", +myLegalName : "Corda Name Service", +nearestCity : "London", +keyStorePassword : "cordacadevpass", +trustStorePassword : "trustpass", +artemisAddress : "localhost:12345", +webAddress : "localhost:12346", +hostNotaryServiceLocally: true, +mapService : { + hostServiceLocally : true, + address : ${artemisAddress}, + identity : ${myLegalName} +} \ No newline at end of file diff --git a/src/main/kotlin/com/r3corda/standalone/Main.kt b/src/main/kotlin/com/r3corda/standalone/Main.kt new file mode 100644 index 0000000000..fcf9c2a53c --- /dev/null +++ b/src/main/kotlin/com/r3corda/standalone/Main.kt @@ -0,0 +1,166 @@ +package com.r3corda.standalone + +import com.google.common.net.HostAndPort +import com.r3corda.core.crypto.Party +import com.r3corda.core.crypto.generateKeyPair +import com.r3corda.core.node.NodeInfo +import com.r3corda.core.node.services.ServiceType +import com.r3corda.node.internal.Node +import com.r3corda.node.serialization.NodeClock +import com.r3corda.node.services.config.NodeConfiguration +import com.r3corda.node.services.messaging.ArtemisMessagingService +import com.r3corda.node.services.network.NetworkMapService +import com.r3corda.node.services.transactions.SimpleNotaryService +import com.typesafe.config.Config +import com.typesafe.config.ConfigFactory +import com.typesafe.config.ConfigRenderOptions +import joptsimple.OptionParser +import org.slf4j.LoggerFactory +import java.io.File +import java.lang.management.ManagementFactory +import java.net.InetAddress +import java.nio.file.Path +import java.nio.file.Paths +import java.time.Clock +import java.time.Instant +import java.time.LocalDate +import java.util.* +import kotlin.reflect.KProperty +import kotlin.reflect.jvm.javaType + +val log = LoggerFactory.getLogger("Main") + +@Suppress("UNCHECKED_CAST") +operator fun Config.getValue(receiver: Any, metadata: KProperty<*>): T { + return when (metadata.returnType.javaType) { + String::class.java -> getString(metadata.name) as T + Int::class.java -> getInt(metadata.name) as T + Long::class.java -> getLong(metadata.name) as T + Double::class.java -> getDouble(metadata.name) as T + Boolean::class.java -> getBoolean(metadata.name) as T + LocalDate::class.java -> LocalDate.parse(getString(metadata.name)) as T + Instant::class.java -> Instant.parse(getString(metadata.name)) as T + HostAndPort::class.java -> HostAndPort.fromString(getString(metadata.name)) as T + Path::class.java -> Paths.get(getString(metadata.name)) as T + else -> throw IllegalArgumentException("Unsupported type ${metadata.returnType}") + } +} + +interface AdvertisedServiceConfig { + val hostServiceLocally: Boolean + val address: HostAndPort + val identity: String +} + +class AdvertisedServiceConfigImpl(conf: Config) : AdvertisedServiceConfig { + override val hostServiceLocally: Boolean by conf + override val address: HostAndPort by conf + override val identity: String by conf +} + +class FullNodeConfiguration(conf: Config) : NodeConfiguration { + val basedir: Path by conf + override val myLegalName: String by conf + override val nearestCity: String by conf + override val exportJMXto: String = "http" + override val keyStorePassword: String by conf + override val trustStorePassword: String by conf + val artemisAddress: HostAndPort by conf + val webAddress: HostAndPort by conf + val hostNotaryServiceLocally: Boolean by conf + val mapService: AdvertisedServiceConfigImpl = AdvertisedServiceConfigImpl(conf.getConfig("mapService")) + val clock: Clock = NodeClock() + + fun createNode(): Node { + val networkMapTarget = ArtemisMessagingService.makeRecipient(mapService.address) + val advertisedServices = mutableSetOf() + if (mapService.hostServiceLocally) advertisedServices.add(NetworkMapService.Type) + if (hostNotaryServiceLocally) advertisedServices.add(SimpleNotaryService.Type) + val networkMapBootstrapIdentity = Party(mapService.identity, generateKeyPair().public) + val networkMapAddress: NodeInfo? = if (mapService.hostServiceLocally) null else NodeInfo(networkMapTarget, networkMapBootstrapIdentity, setOf(NetworkMapService.Type)) + return Node(basedir.toAbsolutePath().normalize(), + artemisAddress, + webAddress, + this, + networkMapAddress, + advertisedServices, + clock + ) + } +} + +object ParamsSpec { + val parser = OptionParser() + + val baseDirectoryArg = + parser.accepts("base-directory", "The directory to put all files under") + .withOptionalArg() + val configFileArg = + parser.accepts("config-file", "The path to the config file") + .withOptionalArg() +} + +fun main(args: Array) { + log.info("Starting Corda Node") + val cmdlineOptions = try { + ParamsSpec.parser.parse(*args) + } catch (ex: Exception) { + log.error("Unable to parse args", ex) + System.exit(1) + return + } + + val baseDirectoryPath = if (cmdlineOptions.has(ParamsSpec.baseDirectoryArg)) Paths.get(cmdlineOptions.valueOf(ParamsSpec.baseDirectoryArg)) else Paths.get(".").normalize() + + val defaultConfig = ConfigFactory.parseResources("reference.conf") + + val configFile = if (cmdlineOptions.has(ParamsSpec.configFileArg)) { + File(cmdlineOptions.valueOf(ParamsSpec.configFileArg)) + } else { + baseDirectoryPath.resolve("node.conf").normalize().toFile() + } + val appConfig = ConfigFactory.parseFile(configFile) + + val cmdlineOverrideMap = HashMap() + cmdlineOverrideMap.put("basedir", baseDirectoryPath.toString()) + val overrideConfig = ConfigFactory.parseMap(cmdlineOverrideMap) + + val mergedAndResolvedConfig = overrideConfig.withFallback(appConfig).withFallback(defaultConfig).resolve() + + log.info("config:\n ${mergedAndResolvedConfig.root().render(ConfigRenderOptions.defaults())}") + val conf = FullNodeConfiguration(mergedAndResolvedConfig) + val dir = conf.basedir.toAbsolutePath().normalize() + logInfo(args, dir) + + val dirFile = dir.toFile() + if (!dirFile.exists()) { + dirFile.mkdirs() + } + + try { + val node = conf.createNode() + node.start() + try { + while (true) Thread.sleep(Long.MAX_VALUE) + } catch(e: InterruptedException) { + node.stop() + } + } catch (e: Exception) { + log.error("Exception during node startup", e) + System.exit(1) + } + System.exit(0) +} + +private fun logInfo(args: Array, dir: Path?) { + log.info("Main class: ${FullNodeConfiguration::class.java.protectionDomain.codeSource.location.toURI().getPath()}") + val info = ManagementFactory.getRuntimeMXBean() + log.info("CommandLine Args: ${info.getInputArguments().joinToString(" ")}") + log.info("Application Args: ${args.joinToString(" ")}") + log.info("bootclasspath: ${info.bootClassPath}") + log.info("classpath: ${info.classPath}") + log.info("VM ${info.vmName} ${info.vmVendor} ${info.vmVersion}") + log.info("Machine: ${InetAddress.getLocalHost().hostName}") + log.info("Working Directory: ${dir}") +} +