Merged in mnesbit-cor-261-deployable-nodes (pull request #269)

Mnesbit cor 261 deployable nodes
This commit is contained in:
Matthew Nesbit 2016-08-08 14:41:34 +01:00
commit 7b63381282
8 changed files with 317 additions and 47 deletions

View File

@ -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,34 @@ buildscript {
}
}
plugins {
// TODO The capsule plugin requires the newer DSL plugin block.It would be nice if we could unify all the plugins into one style,
// but the DSL has some restrictions e.g can't be used on the allprojects section. So we should revisit this if there are improvements in Gradle.
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.3-SNAPSHOT'
}
repositories {
mavenLocal()
mavenCentral()
@ -200,4 +191,48 @@ applicationDistribution.into("bin") {
from(getIRSDemo)
from(getTraderDemo)
fileMode = 0755
}
task createCapsule(type: FatCapsule, dependsOn: 'quasarScan') {
applicationClass 'com.r3corda.node.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'
}
}
task createStandalone(dependsOn: 'createCapsule') << {
copy {
from createCapsule.outputs.getFiles()
from 'config/dev/nameservernode.conf'
into "${buildDir}/standalone/nameserver"
rename 'nameservernode.conf', 'node.conf'
}
copy {
from createCapsule.outputs.getFiles()
from 'config/dev/generalnodea.conf'
into "${buildDir}/standalone/nodea"
rename 'generalnodea.conf', 'node.conf'
}
copy {
from createCapsule.outputs.getFiles()
from 'config/dev/generalnodeb.conf'
into "${buildDir}/standalone/nodeb"
rename 'generalnodeb.conf', 'node.conf'
}
delete("${buildDir}/standalone/runstandalone")
def jarName = createCapsule.outputs.getFiles().getSingleFile().getName()
copy {
from "buildSrc/scripts/runstandalone"
filter { String line -> line.replace("JAR_NAME", jarName) }
filter(org.apache.tools.ant.filters.FixCrLfFilter.class, eol: org.apache.tools.ant.filters.FixCrLfFilter.CrLf.newInstance("lf"))
into "${buildDir}/standalone"
}
}

View File

@ -0,0 +1,15 @@
#!/usr/bin/env bash
set -euo pipefail
trap 'kill $(jobs -p)' SIGINT SIGTERM EXIT
export CAPSULE_CACHE_DIR=cache
pushd nameserver
( java -jar JAR_NAME )&
popd
pushd nodea
( java -jar JAR_NAME )&
popd
pushd nodeb
( java -jar JAR_NAME )&
popd
read -p 'Any key to exit'
kill $(jobs -p)

View File

@ -0,0 +1,14 @@
basedir : "./nodea",
myLegalName : "Bank A",
nearestCity : "London",
keyStorePassword : "cordacadevpass",
trustStorePassword : "trustpass",
artemisAddress : "localhost:31337",
webAddress : "localhost:31339",
hostNotaryServiceLocally: false,
extraAdvertisedServiceIds: "corda.interest_rates",
mapService : {
hostServiceLocally : false,
address : "localhost:12345",
identity : "Notary Service"
}

View File

@ -0,0 +1,14 @@
basedir : "./nodeb",
myLegalName : "Bank B",
nearestCity : "London",
keyStorePassword : "cordacadevpass",
trustStorePassword : "trustpass",
artemisAddress : "localhost:31338",
webAddress : "localhost:31340",
hostNotaryServiceLocally: false,
extraAdvertisedServiceIds: "corda.interest_rates",
mapService : {
hostServiceLocally : false,
address : "localhost:12345",
identity : "Notary Service"
}

View File

@ -0,0 +1,14 @@
basedir : "./nameserver",
myLegalName : "Notary Service",
nearestCity : "London",
keyStorePassword : "cordacadevpass",
trustStorePassword : "trustpass",
artemisAddress : "localhost:12345",
webAddress : "localhost:12346",
hostNotaryServiceLocally: true,
extraAdvertisedServiceIds: "",
mapService : {
hostServiceLocally : true,
address : ${artemisAddress},
identity : ${myLegalName}
}

View File

@ -21,8 +21,15 @@ import javax.net.ssl.*
*/
fun registerWhitelistTrustManager() {
if (Security.getProvider("WhitelistTrustManager") == null) {
Security.addProvider(WhitelistTrustManagerProvider)
WhitelistTrustManagerProvider.register()
}
// Forcibly change the TrustManagerFactory defaultAlgorithm to be us
// This will apply to all code using TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
// Which includes the standard HTTPS implementation and most other SSL code
// TrustManagerFactory.getInstance(WhitelistTrustManagerProvider.originalTrustProviderAlgorithm)) will
// allow access to the original implementation which is normally "PKIX"
Security.setProperty("ssl.TrustManagerFactory.algorithm", "whitelistTrustManager")
}
/**
@ -46,16 +53,16 @@ object WhitelistTrustManagerProvider : Provider("WhitelistTrustManager",
// Add ourselves to whitelist as currently we have to connect to a local ArtemisMQ broker
val host = InetAddress.getLocalHost()
addWhitelistEntry(host.hostName)
}
/**
* Security provider registration function for WhitelistTrustManagerProvider
*/
fun register() {
Security.addProvider(WhitelistTrustManagerProvider)
// Register our custom TrustManagerFactorySpi
put("TrustManagerFactory.whitelistTrustManager", "com.r3corda.core.crypto.WhitelistTrustManagerSpi")
// Forcibly change the TrustManagerFactory defaultAlgorithm to be us
// This will apply to all code using TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
// Which includes the standard HTTPS implementation and most other SSL code
// TrustManagerFactory.getInstance(WhitelistTrustManagerProvider.originalTrustProviderAlgorithm)) will
// allow access to the original implementation which is normally "PKIX"
Security.setProperty("ssl.TrustManagerFactory.algorithm", "whitelistTrustManager")
}
/**

View File

@ -0,0 +1,96 @@
package com.r3corda.node
import com.r3corda.node.services.config.FullNodeConfiguration
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.util.*
val log = LoggerFactory.getLogger("Main")
object ParamsSpec {
val parser = OptionParser()
// The intent of allowing a command line configurable directory and config path is to allow deployment flexibility.
// Other general configuration should live inside the config file unless we regularly need temporary overrides on the command line
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<String>) {
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<String, Any?>() // If we do require a few other command line overrides eg for a nicer development experience they would go inside this map.
if (cmdlineOptions.has(ParamsSpec.baseDirectoryArg)) {
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)
try {
val dirFile = dir.toFile()
if (!dirFile.exists()) {
dirFile.mkdirs()
}
val node = conf.createNode()
node.start()
try {
// TODO create a proper daemon and/or provide some console handler to give interactive commands
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<String>, 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}")
}

View File

@ -1,8 +1,24 @@
package com.r3corda.node.services.config
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.messaging.ArtemisMessagingClient
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 java.nio.file.Path
import java.nio.file.Paths
import java.time.Clock
import java.time.Instant
import java.time.LocalDate
import kotlin.reflect.KProperty
import kotlin.reflect.jvm.javaType
interface NodeConfiguration {
val myLegalName: String
@ -12,8 +28,21 @@ interface NodeConfiguration {
val trustStorePassword: String
}
// Allow the use of "String by config" syntax. TODO: Make it more flexible.
operator fun Config.getValue(receiver: NodeConfigurationFromConfig, metadata: KProperty<*>) = getString(metadata.name)
@Suppress("UNCHECKED_CAST")
operator fun <T> 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}")
}
}
class NodeConfigurationFromConfig(val config: Config = ConfigFactory.load()) : NodeConfiguration {
override val myLegalName: String by config
@ -21,4 +50,50 @@ class NodeConfigurationFromConfig(val config: Config = ConfigFactory.load()) : N
override val nearestCity: String by config
override val keyStorePassword: String by config
override val trustStorePassword: String by config
}
class NameServiceConfig(conf: Config) {
val hostServiceLocally: Boolean by conf
val address: HostAndPort by conf
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 messagingServerAddress: HostAndPort? = if (conf.hasPath("messagingServerAddress")) HostAndPort.fromString(conf.getString("messagingServerAddress")) else null
val hostNotaryServiceLocally: Boolean by conf
val extraAdvertisedServiceIds: String by conf
val mapService: NameServiceConfig = NameServiceConfig(conf.getConfig("mapService"))
val clock: Clock = NodeClock()
fun createNode(): Node {
val networkMapTarget = ArtemisMessagingClient.makeRecipient(mapService.address)
val advertisedServices = mutableSetOf<ServiceType>()
if (mapService.hostServiceLocally) advertisedServices.add(NetworkMapService.Type)
if (hostNotaryServiceLocally) advertisedServices.add(SimpleNotaryService.Type)
if (!extraAdvertisedServiceIds.isNullOrEmpty()) {
for (serviceId in extraAdvertisedServiceIds.split(",")) {
advertisedServices.add(object : ServiceType(serviceId) {})
}
}
// TODO Node startup should not need a full NodeInfo for the remote NetworkMapService provider as bootstrap
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,
messagingServerAddress
)
}
}