mirror of
https://github.com/corda/corda.git
synced 2025-02-07 03:29:19 +00:00
Merge branch 'master' into kat-merge-20180508
This commit is contained in:
commit
1963c29f01
6
.idea/compiler.xml
generated
6
.idea/compiler.xml
generated
@ -12,9 +12,11 @@
|
||||
<module name="bank-of-corda-demo_test" target="1.8" />
|
||||
<module name="behave-tools_main" target="1.8" />
|
||||
<module name="behave-tools_test" target="1.8" />
|
||||
<module name="behave_api" target="1.8" />
|
||||
<module name="behave_behave" target="1.8" />
|
||||
<module name="behave_main" target="1.8" />
|
||||
<module name="behave_scenario" target="1.8" />
|
||||
<module name="behave_smokeTest" target="1.8" />
|
||||
<module name="behave_test" target="1.8" />
|
||||
<module name="blobinspector_main" target="1.8" />
|
||||
<module name="blobinspector_test" target="1.8" />
|
||||
@ -73,6 +75,8 @@
|
||||
<module name="example-code_test" target="1.8" />
|
||||
<module name="experimental-behave_behave" target="1.8" />
|
||||
<module name="experimental-behave_main" target="1.8" />
|
||||
<module name="experimental-behave_scenario" target="1.8" />
|
||||
<module name="experimental-behave_smokeTest" target="1.8" />
|
||||
<module name="experimental-behave_test" target="1.8" />
|
||||
<module name="experimental-kryo-hook_main" target="1.8" />
|
||||
<module name="experimental-kryo-hook_test" target="1.8" />
|
||||
@ -169,6 +173,8 @@
|
||||
<module name="shell_test" target="1.8" />
|
||||
<module name="simm-valuation-demo_integrationTest" target="1.8" />
|
||||
<module name="simm-valuation-demo_main" target="1.8" />
|
||||
<module name="simm-valuation-demo_scenario" target="1.8" />
|
||||
<module name="simm-valuation-demo_scenarioTest" target="1.8" />
|
||||
<module name="simm-valuation-demo_test" target="1.8" />
|
||||
<module name="smoke-test-utils_main" target="1.8" />
|
||||
<module name="smoke-test-utils_test" target="1.8" />
|
||||
|
@ -31,6 +31,7 @@ group 'net.corda.behave'
|
||||
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'kotlin'
|
||||
apply plugin: 'net.corda.plugins.publish-utils'
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
@ -43,15 +44,30 @@ sourceSets {
|
||||
java {
|
||||
compileClasspath += main.output
|
||||
runtimeClasspath += main.output
|
||||
srcDirs = ["src/main/kotlin", "src/scenario/kotlin"]
|
||||
srcDirs = ['src/main/kotlin', 'src/scenario/kotlin']
|
||||
}
|
||||
resources.srcDir file('src/scenario/resources')
|
||||
}
|
||||
smokeTest {
|
||||
kotlin {
|
||||
compileClasspath += main.output + test.output
|
||||
runtimeClasspath += main.output + test.output
|
||||
srcDir file('src/smoke-test/kotlin')
|
||||
}
|
||||
resources {
|
||||
srcDirs = ['src/scenario/resources']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
behaveCompile.extendsFrom testCompile
|
||||
behaveRuntime.extendsFrom testRuntime
|
||||
|
||||
smokeTestCompile.extendsFrom testCompile
|
||||
smokeTestRuntime.extendsFrom testRuntime
|
||||
|
||||
testArtifacts.extendsFrom behaveRuntime
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -91,13 +107,16 @@ dependencies {
|
||||
compile project(':node-api')
|
||||
compile project(':client:rpc')
|
||||
|
||||
// Unit Tests
|
||||
// dependency on CordaHttpToRPC proxy
|
||||
compile project(':testing:qa:behave:tools:rpc-proxy')
|
||||
|
||||
testCompile project(':test-utils')
|
||||
|
||||
// Unit Tests
|
||||
testCompile "junit:junit:$junit_version"
|
||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||
|
||||
// Scenarios / End-to-End Tests
|
||||
|
||||
behaveCompile "info.cukes:cucumber-java8:$cucumber_version"
|
||||
behaveCompile "info.cukes:cucumber-junit:$cucumber_version"
|
||||
behaveCompile "info.cukes:cucumber-picocontainer:$cucumber_version"
|
||||
@ -115,6 +134,11 @@ test {
|
||||
testLogging.showStandardStreams = true
|
||||
}
|
||||
|
||||
task smokeTest(type: Test) {
|
||||
testClassesDirs = sourceSets.smokeTest.output.classesDirs
|
||||
classpath = sourceSets.smokeTest.runtimeClasspath
|
||||
}
|
||||
|
||||
task behaveJar(type: Jar) {
|
||||
baseName "corda-behave"
|
||||
from sourceSets.behave.output
|
||||
@ -133,3 +157,24 @@ task behaveJar(type: Jar) {
|
||||
attributes 'Main-Class': 'net.corda.behave.scenarios.ScenarioRunner'
|
||||
}
|
||||
}
|
||||
|
||||
task apiJar(type: Jar, dependsOn: classes) {
|
||||
baseName "corda-behave-api"
|
||||
from sourceSets.behave.output
|
||||
from {
|
||||
configurations.behaveCompile.collect {
|
||||
it.isDirectory() ? it : zipTree(it)
|
||||
}
|
||||
}
|
||||
from project(':client:rpc').configurations.compile.collect { zipTree it }
|
||||
with jar
|
||||
include 'net/corda/behave/scenarios/**'
|
||||
include 'cucumber/api/**'
|
||||
include 'io/github/lukehutch/**'
|
||||
exclude '**/features/**'
|
||||
exclude '**/scripts/**'
|
||||
}
|
||||
|
||||
artifacts {
|
||||
testArtifacts apiJar
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ set -x
|
||||
# For example:
|
||||
# corda-master => git clone https://github.com/corda/corda
|
||||
# r3corda-master => git clone https://github.com/corda/enterprise
|
||||
VERSION=corda-master
|
||||
VERSION=r3corda-master
|
||||
STAGING_DIR=~/staging
|
||||
CORDA_DIR=${STAGING_DIR}/corda/${VERSION}
|
||||
CORDAPP_DIR=${CORDA_DIR}/apps
|
||||
@ -18,18 +18,32 @@ mkdir -p ${CORDA_DIR}
|
||||
mkdir -p ${CORDAPP_DIR}
|
||||
mkdir -p ${DRIVERS_DIR}
|
||||
|
||||
# Copy Corda capsule into deps
|
||||
# Copy Corda capsule into staging
|
||||
cd ../..
|
||||
./gradlew clean :node:capsule:buildCordaJar :finance:jar
|
||||
./gradlew :node:capsule:buildCordaJar :finance:jar
|
||||
cp -v $(ls node/capsule/build/libs/corda-*.jar | tail -n1) ${CORDA_DIR}/corda.jar
|
||||
|
||||
# Copy finance library
|
||||
cp -v $(ls finance/build/libs/corda-finance-*.jar | tail -n1) ${CORDAPP_DIR}
|
||||
|
||||
# Copy sample Cordapps
|
||||
./gradlew samples:simm-valuation-demo:jar
|
||||
cp -v $(ls samples/simm-valuation-demo/build/libs/simm-valuation-demo-*.jar | tail -n1) ${CORDAPP_DIR}
|
||||
|
||||
# Download database drivers
|
||||
curl "https://search.maven.org/remotecontent?filepath=com/h2database/h2/1.4.196/h2-1.4.196.jar" > ${DRIVERS_DIR}/h2-1.4.196.jar
|
||||
curl -L "http://central.maven.org/maven2/org/postgresql/postgresql/42.1.4/postgresql-42.1.4.jar" > ${DRIVERS_DIR}/postgresql-42.1.4.jar
|
||||
curl -L "https://github.com/Microsoft/mssql-jdbc/releases/download/v6.2.2/mssql-jdbc-6.2.2.jre8.jar" > ${DRIVERS_DIR}/mssql-jdbc-6.2.2.jre8.jar
|
||||
|
||||
# Build Network Bootstrapper
|
||||
./gradlew buildBootstrapperJar
|
||||
cp -v $(ls tools/bootstrapper/build/libs/*.jar | tail -n1) ${CORDA_DIR}/network-bootstrapper.jar
|
||||
|
||||
# build and distribute Doorman/NMS
|
||||
./gradlew network-management:capsule:buildDoormanJAR
|
||||
cp -v $(ls network-management/capsule/build/libs/doorman-*.jar | tail -n1) ${CORDA_DIR}/doorman.jar
|
||||
|
||||
# build and distribute DB Migration tool
|
||||
./gradlew tools:dbmigration:shadowJar
|
||||
cp -v $(ls tools/dbmigration/build/libs/*migration-*.jar | tail -n1) ${CORDA_DIR}/dbmigration.jar
|
||||
|
||||
|
@ -12,6 +12,7 @@ package net.corda.behave.database
|
||||
|
||||
import net.corda.behave.database.configuration.H2ConfigurationTemplate
|
||||
import net.corda.behave.database.configuration.PostgresConfigurationTemplate
|
||||
import net.corda.behave.database.configuration.SqlServerConfigurationTemplate
|
||||
import net.corda.behave.node.configuration.Configuration
|
||||
import net.corda.behave.node.configuration.DatabaseConfiguration
|
||||
import net.corda.behave.service.database.H2Service
|
||||
@ -29,8 +30,18 @@ enum class DatabaseType(val settings: DatabaseSettings) {
|
||||
}
|
||||
),
|
||||
|
||||
SQL_SERVER(DatabaseSettings()
|
||||
.withDatabase(SqlServerService.database)
|
||||
.withDriver(SqlServerService.driver)
|
||||
.withSchema(SqlServerService.schema)
|
||||
.withUser(SqlServerService.username)
|
||||
.withConfigTemplate(SqlServerConfigurationTemplate())
|
||||
.withServiceInitiator {
|
||||
PostgreSQLService("postgres-${it.name}", it.database.port, it.database.password)
|
||||
}
|
||||
),
|
||||
|
||||
POSTGRES(DatabaseSettings()
|
||||
.withDatabase(PostgreSQLService.database)
|
||||
.withDriver(PostgreSQLService.driver)
|
||||
.withSchema(PostgreSQLService.schema)
|
||||
.withUser(PostgreSQLService.username)
|
||||
@ -48,8 +59,9 @@ enum class DatabaseType(val settings: DatabaseSettings) {
|
||||
|
||||
companion object {
|
||||
|
||||
fun fromName(name: String): DatabaseType? = when (name.toLowerCase()) {
|
||||
fun fromName(name: String): DatabaseType? = when (name.replace("[ _-]".toRegex(), "").toLowerCase()) {
|
||||
"h2" -> H2
|
||||
"sqlserver" -> SQL_SERVER
|
||||
"postgres" -> POSTGRES
|
||||
"postgresql" -> POSTGRES
|
||||
else -> null
|
||||
|
@ -0,0 +1,38 @@
|
||||
/*
|
||||
* R3 Proprietary and Confidential
|
||||
*
|
||||
* Copyright (c) 2018 R3 Limited. All rights reserved.
|
||||
*
|
||||
* The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law.
|
||||
*
|
||||
* Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited.
|
||||
*/
|
||||
|
||||
package net.corda.behave.database.configuration
|
||||
|
||||
import net.corda.behave.database.DatabaseConfigurationTemplate
|
||||
import net.corda.behave.node.configuration.DatabaseConfiguration
|
||||
|
||||
class SqlServerConfigurationTemplate : DatabaseConfigurationTemplate() {
|
||||
|
||||
override val connectionString: (DatabaseConfiguration) -> String
|
||||
get() = { "jdbc:sqlserver://${it.host}:${it.port};database=${it.database}" }
|
||||
|
||||
override val config: (DatabaseConfiguration) -> String
|
||||
get() = {
|
||||
"""
|
||||
|dataSourceProperties = {
|
||||
| dataSourceClassName = "com.microsoft.sqlserver.jdbc.SQLServerDataSource"
|
||||
| dataSource.url = "${connectionString(it)}"
|
||||
| dataSource.user = "${it.username}"
|
||||
| dataSource.password = "${it.password}"
|
||||
|}
|
||||
|database = {
|
||||
| initialiseSchema=true
|
||||
| transactionIsolationLevel = READ_COMMITTED
|
||||
| schema="${it.schema}"
|
||||
|}
|
||||
"""
|
||||
}
|
||||
|
||||
}
|
@ -10,6 +10,7 @@
|
||||
|
||||
package net.corda.behave.file
|
||||
|
||||
import net.corda.core.internal.div
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
|
||||
@ -19,3 +20,9 @@ val currentDirectory: Path
|
||||
// location of Corda distributions and Drivers dependencies
|
||||
val stagingRoot: Path
|
||||
get() = System.getProperty("STAGING_ROOT")?.let { Paths.get(it) } ?: currentDirectory
|
||||
|
||||
val doormanConfigDirectory: Path
|
||||
get() = currentDirectory / "src" / "main" / "resources" / "doorman"
|
||||
|
||||
val tmpDirectory: Path
|
||||
get() = System.getProperty("TMPDIR")?.let { Paths.get(it) } ?: Paths.get("/tmp")
|
@ -11,20 +11,23 @@
|
||||
package net.corda.behave.network
|
||||
|
||||
import net.corda.behave.database.DatabaseType
|
||||
import net.corda.behave.file.LogSource
|
||||
import net.corda.behave.file.currentDirectory
|
||||
import net.corda.behave.file.stagingRoot
|
||||
import net.corda.behave.file.*
|
||||
import net.corda.behave.monitoring.PatternWatch
|
||||
import net.corda.behave.node.Distribution
|
||||
import net.corda.behave.node.Node
|
||||
import net.corda.behave.node.configuration.NotaryType
|
||||
import net.corda.behave.process.Command
|
||||
import net.corda.behave.process.JarCommand
|
||||
import net.corda.core.CordaException
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.minutes
|
||||
import org.apache.commons.io.FileUtils
|
||||
import net.corda.core.utilities.seconds
|
||||
import java.io.Closeable
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption
|
||||
import java.time.Duration
|
||||
import java.time.Instant
|
||||
import java.time.ZoneOffset.UTC
|
||||
@ -46,11 +49,16 @@ class Network private constructor(
|
||||
|
||||
private var hasError = false
|
||||
|
||||
private var isDoormanNMSRunning = false
|
||||
|
||||
private lateinit var doormanNMS: JarCommand
|
||||
|
||||
init {
|
||||
targetDirectory.createDirectories()
|
||||
}
|
||||
|
||||
class Builder internal constructor(
|
||||
private val networkType: Distribution.Type,
|
||||
private val timeout: Duration
|
||||
) {
|
||||
|
||||
@ -68,7 +76,9 @@ class Network private constructor(
|
||||
distribution: Distribution = Distribution.MASTER,
|
||||
databaseType: DatabaseType = DatabaseType.H2,
|
||||
notaryType: NotaryType = NotaryType.NONE,
|
||||
issuableCurrencies: List<String> = emptyList()
|
||||
issuableCurrencies: List<String> = emptyList(),
|
||||
compatibilityZoneURL: String? = null,
|
||||
withRPCProxy: Boolean = false
|
||||
): Builder {
|
||||
return addNode(Node.new()
|
||||
.withName(name)
|
||||
@ -76,6 +86,8 @@ class Network private constructor(
|
||||
.withDatabaseType(databaseType)
|
||||
.withNotaryType(notaryType)
|
||||
.withIssuableCurrencies(*issuableCurrencies.toTypedArray())
|
||||
.withRPCProxy(withRPCProxy)
|
||||
.withNetworkMap(compatibilityZoneURL)
|
||||
)
|
||||
}
|
||||
|
||||
@ -95,8 +107,11 @@ class Network private constructor(
|
||||
if (!network.configureNodes()) {
|
||||
throw CordaException("Unable to configure nodes in Corda network. Please check logs in $directory")
|
||||
}
|
||||
network.bootstrapLocalNetwork()
|
||||
|
||||
if (networkType == Distribution.Type.R3_CORDA)
|
||||
network.bootstrapDoorman()
|
||||
else
|
||||
network.bootstrapLocalNetwork()
|
||||
return network
|
||||
}
|
||||
}
|
||||
@ -104,7 +119,7 @@ class Network private constructor(
|
||||
fun copyDatabaseDrivers() {
|
||||
val driverDirectory = (targetDirectory / "libs").createDirectories()
|
||||
log.info("Copying database drivers from $stagingRoot/drivers to $driverDirectory")
|
||||
FileUtils.copyDirectory((stagingRoot / "drivers").toFile(), driverDirectory.toFile())
|
||||
Files.copy((stagingRoot / "drivers"), driverDirectory, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
|
||||
fun configureNodes(): Boolean {
|
||||
@ -125,8 +140,152 @@ class Network private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method performs the configuration steps defined in the R3 Corda Network Management readme:
|
||||
* https://github.com/corda/enterprise/blob/master/network-management/README.md
|
||||
* using Local signing and "Auto Approval" mode
|
||||
*/
|
||||
private fun bootstrapDoorman() {
|
||||
|
||||
// WARNING!! Need to use the correct bootstrapper
|
||||
// only if using OS nodes (need to choose the latest version)
|
||||
val r3node = nodes.values
|
||||
.find { it.config.distribution.type == Distribution.Type.R3_CORDA } ?: throw CordaRuntimeException("Missing R3 distribution node")
|
||||
val distribution = r3node.config.distribution
|
||||
|
||||
// Copy over reference configuration files used in bootstrapping
|
||||
val source = doormanConfigDirectory
|
||||
val doormanTargetDirectory = targetDirectory / "doorman"
|
||||
source.toFile().copyRecursively(doormanTargetDirectory.toFile(), true)
|
||||
|
||||
// 1. Create key stores for local signer
|
||||
|
||||
// java -jar doorman-<version>.jar --mode ROOT_KEYGEN
|
||||
log.info("Doorman target directory: $doormanTargetDirectory")
|
||||
runCommand(JarCommand(distribution.doormanJar,
|
||||
arrayOf("--config-file", "$doormanConfigDirectory/node-init.conf", "--mode", "ROOT_KEYGEN", "--trust-store-password", "password"),
|
||||
doormanTargetDirectory, timeout))
|
||||
|
||||
// java -jar doorman-<version>.jar --mode CA_KEYGEN
|
||||
runCommand(JarCommand(distribution.doormanJar,
|
||||
arrayOf("--config-file", "$doormanConfigDirectory/node-init.conf", "--mode", "CA_KEYGEN"),
|
||||
doormanTargetDirectory, timeout))
|
||||
|
||||
// 2. Start the doorman service for notary registration
|
||||
doormanNMS = JarCommand(distribution.doormanJar,
|
||||
arrayOf("--config-file", "$doormanConfigDirectory/node-init.conf"),
|
||||
doormanTargetDirectory, timeout)
|
||||
|
||||
val doormanCommand = runCommand(doormanNMS, noWait = true)
|
||||
log.info("Waiting for DoormanNMS to be alive")
|
||||
|
||||
PatternWatch(doormanCommand.output, "Network management web services started on").await(30.seconds)
|
||||
log.info("DoormanNMS up and running")
|
||||
|
||||
// Notary Nodes
|
||||
val notaryNodes = nodes.values.filter { it.config.notary.notaryType != NotaryType.NONE }
|
||||
notaryNodes.forEach { notaryNode ->
|
||||
val notaryTargetDirectory = targetDirectory / notaryNode.config.name
|
||||
log.info("Notary target directory: $notaryTargetDirectory")
|
||||
|
||||
// 3. Create notary node and register with the doorman
|
||||
runCommand(JarCommand(distribution.cordaJar,
|
||||
arrayOf("--initial-registration",
|
||||
"--base-directory", "$notaryTargetDirectory",
|
||||
"--network-root-truststore", "../doorman/certificates/distribute-nodes/network-root-truststore.jks",
|
||||
"--network-root-truststore-password", "password"),
|
||||
notaryTargetDirectory, timeout))
|
||||
|
||||
// 4. Generate node info files for notary nodes
|
||||
runCommand(JarCommand(distribution.cordaJar,
|
||||
arrayOf("--just-generate-node-info",
|
||||
"--base-directory", "$notaryTargetDirectory"),
|
||||
notaryTargetDirectory, timeout))
|
||||
|
||||
// cp (or ln -s) nodeInfo* notary-node-info
|
||||
val nodeInfoFile = notaryTargetDirectory.toFile().listFiles { _, filename -> filename.matches("nodeInfo-.+".toRegex()) }.firstOrNull() ?: throw CordaRuntimeException("Missing notary nodeInfo file")
|
||||
|
||||
Files.copy(nodeInfoFile.toPath(), (notaryTargetDirectory / "notary-node-info"), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
|
||||
// exit Doorman process
|
||||
doormanCommand.interrupt()
|
||||
doormanCommand.waitFor()
|
||||
|
||||
// 5. Add notary identities to the network parameters
|
||||
|
||||
// 6. Load initial network parameters file for network map service
|
||||
val networkParamsConfig = if (notaryNodes.isEmpty()) "network-parameters-without-notary.conf" else "network-parameters.conf"
|
||||
val updateNetworkParams = JarCommand(distribution.doormanJar,
|
||||
arrayOf("--config-file", "$doormanTargetDirectory/node.conf", "--set-network-parameters", "$doormanTargetDirectory/$networkParamsConfig"),
|
||||
doormanTargetDirectory, timeout)
|
||||
runCommand(updateNetworkParams)
|
||||
|
||||
// 7. Start a fully configured Doorman / NMS
|
||||
doormanNMS = JarCommand(distribution.doormanJar,
|
||||
arrayOf("--config-file", "$doormanConfigDirectory/node.conf"),
|
||||
doormanTargetDirectory, timeout)
|
||||
|
||||
val doormanNMSCommand = runCommand(doormanNMS, noWait = true)
|
||||
log.info("Waiting for DoormanNMS to be alive")
|
||||
|
||||
PatternWatch(doormanNMSCommand.output, "Network management web services started on").await(30.seconds)
|
||||
log.info("DoormanNMS up and running")
|
||||
|
||||
// 8. Register other participant nodes
|
||||
val partyNodes = nodes.values.filter { it.config.notary.notaryType == NotaryType.NONE }
|
||||
partyNodes.forEach { partyNode ->
|
||||
val partyTargetDirectory = targetDirectory / partyNode.config.name
|
||||
log.info("Party target directory: $partyTargetDirectory")
|
||||
|
||||
// 3. Create notary node and register with the doorman
|
||||
runCommand(JarCommand(distribution.cordaJar,
|
||||
arrayOf("--initial-registration",
|
||||
"--network-root-truststore", "../doorman/certificates/distribute-nodes/network-root-truststore.jks",
|
||||
"--network-root-truststore-password", "password",
|
||||
"--base-directory", "$partyTargetDirectory"),
|
||||
partyTargetDirectory, timeout))
|
||||
}
|
||||
|
||||
isDoormanNMSRunning = true
|
||||
}
|
||||
|
||||
private fun runCommand(command: Command, noWait: Boolean = false): Command {
|
||||
if (command is JarCommand) {
|
||||
log.info("Checking existence of jar file:", command.jarFile)
|
||||
if (!command.jarFile.exists()) {
|
||||
throw IllegalStateException("Jar file does not exist: ${command.jarFile}")
|
||||
}
|
||||
}
|
||||
log.info("Running command: {}", command)
|
||||
command.output.subscribe {
|
||||
if (it.contains("Exception")) {
|
||||
log.warn("Found error in output; interrupting command execution ...\n{}", it)
|
||||
command.interrupt()
|
||||
}
|
||||
}
|
||||
command.start()
|
||||
if (!noWait) {
|
||||
if (!command.waitFor()) {
|
||||
hasError = true
|
||||
error("Failed to execute command") {
|
||||
val matches = LogSource(targetDirectory)
|
||||
.find(".*[Ee]xception.*")
|
||||
.groupBy { it.filename.toAbsolutePath() }
|
||||
for (match in matches) {
|
||||
log.info("Log(${match.key}):\n${match.value.joinToString("\n") { it.contents }}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.info("Command executed successfully")
|
||||
}
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
private fun bootstrapLocalNetwork() {
|
||||
val bootstrapper = nodes.values
|
||||
.filter { it.config.distribution.type != Distribution.Type.R3_CORDA }
|
||||
.sortedByDescending { it.config.distribution.version }
|
||||
.first()
|
||||
.config.distribution.networkBootstrapper
|
||||
@ -143,26 +302,25 @@ class Network private constructor(
|
||||
targetDirectory,
|
||||
timeout
|
||||
)
|
||||
log.info("Running command: {}", command)
|
||||
command.output.subscribe {
|
||||
if (it.contains("Exception")) {
|
||||
log.warn("Found error in output; interrupting bootstrapping action ...\n{}", it)
|
||||
command.interrupt()
|
||||
}
|
||||
runCommand(command)
|
||||
}
|
||||
|
||||
private fun bootstrapRPCProxy(node: Node) {
|
||||
val cordaDistribution = node.config.distribution.path
|
||||
val rpcProxyPortNo = node.config.nodeInterface.rpcProxy
|
||||
|
||||
val startProxyScript = cordaDistribution / "startRPCproxy.sh"
|
||||
if (startProxyScript.exists()) {
|
||||
log.info("Bootstrapping RPC proxy, please wait ...")
|
||||
val rpcProxyLogFile = targetDirectory / node.config.name / "logs" / "startRPCproxy.log"
|
||||
val rpcProxyCommand = Command(listOf("$startProxyScript", "$cordaDistribution", "$rpcProxyPortNo", ">>$rpcProxyLogFile", "2>&1"),
|
||||
cordaDistribution,
|
||||
timeout
|
||||
)
|
||||
runCommand(rpcProxyCommand)
|
||||
}
|
||||
command.start()
|
||||
if (!command.waitFor()) {
|
||||
hasError = true
|
||||
error("Failed to bootstrap network") {
|
||||
val matches = LogSource(targetDirectory)
|
||||
.find(".*[Ee]xception.*")
|
||||
.groupBy { it.filename.toAbsolutePath() }
|
||||
for (match in matches) {
|
||||
log.info("Log(${match.key}):\n${match.value.joinToString("\n") { it.contents }}")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log.info("Network set-up completed")
|
||||
else {
|
||||
log.warn("Missing RPC proxy startup script. Continuing ...")
|
||||
}
|
||||
}
|
||||
|
||||
@ -205,6 +363,8 @@ class Network private constructor(
|
||||
for (node in nodes.values) {
|
||||
log.info("Starting node [{}]", node.config.name)
|
||||
node.start()
|
||||
if (node.rpcProxy)
|
||||
bootstrapRPCProxy(node)
|
||||
}
|
||||
}
|
||||
|
||||
@ -269,8 +429,28 @@ class Network private constructor(
|
||||
log.info("Shutting down nodes ...")
|
||||
for (node in nodes.values) {
|
||||
node.shutDown()
|
||||
if (node.rpcProxy) {
|
||||
log.info("Shutting down RPC proxy ...")
|
||||
try {
|
||||
val rpcProxyPortNo = node.config.nodeInterface.rpcProxy
|
||||
val pid = Files.lines(tmpDirectory / "rpcProxy-pid-$rpcProxyPortNo").findFirst().get()
|
||||
// TODO: consider generic implementation to support non *nix platforms
|
||||
Command(listOf("kill", "-9", "$pid")).run()
|
||||
(tmpDirectory / "rpcProxy-pid-$rpcProxyPortNo").deleteIfExists()
|
||||
}
|
||||
catch (e: Exception) {
|
||||
log.warn("Unable to locate PID file: ${e.message}")
|
||||
}
|
||||
}
|
||||
}
|
||||
cleanup()
|
||||
|
||||
if (isDoormanNMSRunning) {
|
||||
log.info("Shutting down R3 Corda NMS server ...")
|
||||
doormanNMS.kill()
|
||||
}
|
||||
|
||||
if (System.getProperty("DISABLE_CLEANUP") == null) // useful for re-starting and troubleshooting failure issues
|
||||
cleanup()
|
||||
}
|
||||
|
||||
fun use(action: (Network) -> Unit) {
|
||||
@ -295,7 +475,7 @@ class Network private constructor(
|
||||
val log = contextLogger()
|
||||
const val CLEANUP_ON_ERROR = false
|
||||
|
||||
fun new(timeout: Duration = 2.minutes
|
||||
): Builder = Builder(timeout)
|
||||
fun new(type: Distribution.Type = Distribution.Type.CORDA, timeout: Duration = 2.minutes
|
||||
): Builder = Builder(type, timeout)
|
||||
}
|
||||
}
|
@ -11,6 +11,7 @@
|
||||
package net.corda.behave.node
|
||||
|
||||
import net.corda.behave.file.stagingRoot
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.internal.copyTo
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
@ -24,6 +25,11 @@ import java.nio.file.Path
|
||||
*/
|
||||
class Distribution private constructor(
|
||||
|
||||
/**
|
||||
* The distribution type of Corda: Open Source or R3 Corda (Enterprise)
|
||||
*/
|
||||
val type: Type,
|
||||
|
||||
/**
|
||||
* The version string of the Corda distribution.
|
||||
*/
|
||||
@ -65,6 +71,16 @@ class Distribution private constructor(
|
||||
*/
|
||||
val networkBootstrapper: Path = path / "network-bootstrapper.jar"
|
||||
|
||||
/**
|
||||
* The path to the doorman jar (R3 Corda only).
|
||||
*/
|
||||
val doormanJar: Path = path / "doorman.jar"
|
||||
|
||||
/**
|
||||
* The path to the DB migration jar (R3 Corda only).
|
||||
*/
|
||||
val dbMigrationJar: Path = nodePrefix / version / "dbmigration.jar"
|
||||
|
||||
/**
|
||||
* Ensure that the distribution is available on disk.
|
||||
*/
|
||||
@ -89,6 +105,11 @@ class Distribution private constructor(
|
||||
*/
|
||||
override fun toString() = "Corda(version = $version, path = $cordaJar)"
|
||||
|
||||
enum class Type {
|
||||
CORDA,
|
||||
R3_CORDA
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val log = contextLogger()
|
||||
@ -97,38 +118,46 @@ class Distribution private constructor(
|
||||
|
||||
private val nodePrefix = stagingRoot / "corda"
|
||||
|
||||
val MASTER = fromJarFile("corda-master")
|
||||
val MASTER = fromJarFile(Type.CORDA, "corda-master")
|
||||
val R3_MASTER = fromJarFile(Type.R3_CORDA, "r3corda-master")
|
||||
|
||||
/**
|
||||
* Get representation of a Corda distribution from Artifactory based on its version string.
|
||||
* @param type The Corda distribution type.
|
||||
* @param version The version of the Corda distribution.
|
||||
*/
|
||||
fun fromArtifactory(version: String): Distribution {
|
||||
val url = URL("https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases/net/corda/corda/$version/corda-$version.jar")
|
||||
fun fromArtifactory(type: Type, version: String): Distribution {
|
||||
val url =
|
||||
when (type) {
|
||||
Type.CORDA -> URL("https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases/net/corda/corda/$version/corda-$version.jar")
|
||||
Type.R3_CORDA -> URL("https://ci-artifactory.corda.r3cev.com/artifactory/r3-corda-releases/com/r3/corda/corda/$version/corda-$version.jar")
|
||||
}
|
||||
log.info("Artifactory URL: $url\n")
|
||||
val distribution = Distribution(version, url = url)
|
||||
val distribution = Distribution(type, version, url = url)
|
||||
distributions.add(distribution)
|
||||
return distribution
|
||||
}
|
||||
|
||||
/**
|
||||
* Get representation of a Corda distribution based on its version string and fat JAR path.
|
||||
* @param type The Corda distribution type.
|
||||
* @param version The version of the Corda distribution.
|
||||
* @param jarFile The path to the Corda fat JAR.
|
||||
*/
|
||||
fun fromJarFile(version: String, jarFile: Path? = null): Distribution {
|
||||
val distribution = Distribution(version, file = jarFile)
|
||||
fun fromJarFile(type: Type, version: String, jarFile: Path? = null): Distribution {
|
||||
val distribution = Distribution(type, version, file = jarFile)
|
||||
distributions.add(distribution)
|
||||
return distribution
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Corda distribution from a Docker image file.
|
||||
* @param type The Corda distribution type.
|
||||
* @param baseImage The name (eg. corda) of the Corda distribution.
|
||||
* @param imageTag The version (github commit id or corda version) of the Corda distribution.
|
||||
*/
|
||||
fun fromDockerImage(baseImage: String, imageTag: String): Distribution {
|
||||
val distribution = Distribution(version = imageTag, baseImage = baseImage)
|
||||
fun fromDockerImage(type: Type, baseImage: String, imageTag: String): Distribution {
|
||||
val distribution = Distribution(type, version = imageTag, baseImage = baseImage)
|
||||
distributions.add(distribution)
|
||||
return distribution
|
||||
}
|
||||
@ -139,8 +168,11 @@ class Distribution private constructor(
|
||||
*/
|
||||
fun fromVersionString(version: String): Distribution = when (version) {
|
||||
"master" -> MASTER
|
||||
"corda-3.0" -> fromArtifactory(version)
|
||||
else -> fromJarFile(version)
|
||||
"r3-master" -> R3_MASTER
|
||||
"corda-3.0" -> fromArtifactory(Type.CORDA, version)
|
||||
"corda-3.1" -> fromArtifactory(Type.CORDA, version)
|
||||
"R3.CORDA-3.0.0-DEV-PREVIEW-3" -> fromArtifactory(Type.R3_CORDA, version)
|
||||
else -> distributions.firstOrNull { it.version == version } ?: throw CordaRuntimeException("Unable to locate Corda distribution for version: $version")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,16 +18,20 @@ import net.corda.behave.file.stagingRoot
|
||||
import net.corda.behave.monitoring.PatternWatch
|
||||
import net.corda.behave.node.configuration.*
|
||||
import net.corda.behave.process.JarCommand
|
||||
import net.corda.behave.process.JarCommandWithMain
|
||||
import net.corda.behave.service.Service
|
||||
import net.corda.behave.service.ServiceSettings
|
||||
import net.corda.behave.service.proxy.CordaRPCProxyClient
|
||||
import net.corda.behave.ssh.MonitoringSSHClient
|
||||
import net.corda.behave.ssh.SSHClient
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.exists
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.minutes
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.core.utilities.seconds
|
||||
import org.apache.commons.io.FileUtils
|
||||
@ -42,7 +46,9 @@ import java.util.concurrent.CountDownLatch
|
||||
class Node(
|
||||
val config: Configuration,
|
||||
private val rootDirectory: Path = currentDirectory,
|
||||
private val settings: ServiceSettings = ServiceSettings()
|
||||
private val settings: ServiceSettings = ServiceSettings(),
|
||||
val rpcProxy: Boolean = false,
|
||||
val networkType: Distribution.Type
|
||||
) {
|
||||
|
||||
private val log = loggerFor<Node>()
|
||||
@ -89,16 +95,40 @@ class Node(
|
||||
log.info("Configuring {} ...", this)
|
||||
serviceDependencies.addAll(config.database.type.dependencies(config))
|
||||
config.distribution.ensureAvailable()
|
||||
config.writeToFile(rootDirectory / "${config.name}_node.conf")
|
||||
if (networkType == Distribution.Type.CORDA) {
|
||||
config.writeToFile(rootDirectory / "${config.name}_node.conf")
|
||||
}
|
||||
else {
|
||||
val nodeDirectory = (rootDirectory / config.name).createDirectories()
|
||||
config.writeToFile(nodeDirectory / "node.conf")
|
||||
}
|
||||
installApps()
|
||||
}
|
||||
|
||||
private fun initialiseDatabase(database: DatabaseConfiguration) {
|
||||
val driversDir = runtimeDirectory / "drivers"
|
||||
log.info("Creating directory for drivers: $driversDir")
|
||||
driversDir.toFile().mkdirs()
|
||||
log.info("Initialising database for R3 Corda node: $database")
|
||||
val command = JarCommandWithMain(listOf(config.distribution.dbMigrationJar, rootDirectory / "libs" / database.type.driverJar!!),
|
||||
"com.r3.corda.dbmigration.DBMigration",
|
||||
arrayOf("--base-directory", "$runtimeDirectory", "--execute-migration"),
|
||||
runtimeDirectory, 2.minutes)
|
||||
command.run()
|
||||
}
|
||||
|
||||
fun start(): Boolean {
|
||||
if (!startDependencies()) {
|
||||
return false
|
||||
}
|
||||
log.info("Starting {} ...", this)
|
||||
return try {
|
||||
// initialise database via DB migration tool
|
||||
if (config.distribution.type == Distribution.Type.R3_CORDA &&
|
||||
config.database.type != DatabaseType.H2) {
|
||||
initialiseDatabase(config.database)
|
||||
}
|
||||
// launch node itself
|
||||
command.start()
|
||||
isStarted = true
|
||||
true
|
||||
@ -182,6 +212,20 @@ class Node(
|
||||
return result ?: error("Failed to run RPC action")
|
||||
}
|
||||
|
||||
fun <T> http(action: (CordaRPCOps) -> T): T {
|
||||
val address = config.nodeInterface
|
||||
val targetHost = NetworkHostAndPort(address.host, address.rpcProxy)
|
||||
log.info("Establishing HTTP connection to ${targetHost.host} on port ${targetHost.port} ...")
|
||||
try {
|
||||
return action(CordaRPCProxyClient(targetHost))
|
||||
}
|
||||
catch (e: Exception) {
|
||||
log.warn("Failed to invoke http endpoint: ", e)
|
||||
e.printStackTrace()
|
||||
error("Failed to run http action")
|
||||
}
|
||||
}
|
||||
|
||||
override fun toString(): String {
|
||||
return "Node(name = ${config.name}, version = ${config.distribution.version})"
|
||||
}
|
||||
@ -248,6 +292,8 @@ class Node(
|
||||
|
||||
private var notaryType = NotaryType.NONE
|
||||
|
||||
private var compatibilityZoneURL: String? = null
|
||||
|
||||
private val issuableCurrencies = mutableListOf<String>()
|
||||
|
||||
private var location: String = "London"
|
||||
@ -262,6 +308,10 @@ class Node(
|
||||
|
||||
private var timeout = Duration.ofSeconds(60)
|
||||
|
||||
private var rpcProxy = false
|
||||
|
||||
var networkType = distribution.type
|
||||
|
||||
fun withName(newName: String): Builder {
|
||||
name = newName
|
||||
return this
|
||||
@ -269,6 +319,7 @@ class Node(
|
||||
|
||||
fun withDistribution(newDistribution: Distribution): Builder {
|
||||
distribution = newDistribution
|
||||
networkType = distribution.type
|
||||
return this
|
||||
}
|
||||
|
||||
@ -282,6 +333,16 @@ class Node(
|
||||
return this
|
||||
}
|
||||
|
||||
fun withNetworkMap(newCompatibilityZoneURL: String?): Builder {
|
||||
compatibilityZoneURL = newCompatibilityZoneURL
|
||||
return this
|
||||
}
|
||||
|
||||
fun withNetworkType(newNetworkType: Distribution.Type): Builder {
|
||||
networkType = newNetworkType
|
||||
return this
|
||||
}
|
||||
|
||||
fun withIssuableCurrencies(vararg currencies: String): Builder {
|
||||
issuableCurrencies.addAll(currencies)
|
||||
return this
|
||||
@ -318,9 +379,18 @@ class Node(
|
||||
return this
|
||||
}
|
||||
|
||||
fun withRPCProxy(withRPCProxy: Boolean): Builder {
|
||||
rpcProxy = withRPCProxy
|
||||
return this
|
||||
}
|
||||
|
||||
fun build(): Node {
|
||||
val name = name ?: error("Node name not set")
|
||||
val directory = directory ?: error("Runtime directory not set")
|
||||
val compatibilityZoneURL =
|
||||
if (networkType == Distribution.Type.R3_CORDA)
|
||||
compatibilityZoneURL ?: "http://localhost:1300"
|
||||
else null
|
||||
return Node(
|
||||
Configuration(
|
||||
name,
|
||||
@ -335,11 +405,14 @@ class Node(
|
||||
),
|
||||
configElements = *arrayOf(
|
||||
NotaryConfiguration(notaryType),
|
||||
NetworkMapConfiguration(compatibilityZoneURL),
|
||||
CurrencyConfiguration(issuableCurrencies)
|
||||
)
|
||||
),
|
||||
directory,
|
||||
ServiceSettings(timeout)
|
||||
ServiceSettings(timeout),
|
||||
rpcProxy = rpcProxy,
|
||||
networkType = networkType
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -36,7 +36,7 @@ class Configuration(
|
||||
vararg configElements: ConfigurationTemplate
|
||||
) {
|
||||
|
||||
private val developerMode = true
|
||||
private val developerMode = (distribution.type == Distribution.Type.CORDA)
|
||||
|
||||
val cordaX500Name: CordaX500Name by lazy({
|
||||
CordaX500Name(name, location, country)
|
||||
|
@ -18,6 +18,7 @@ data class NetworkInterface(
|
||||
val sshPort: Int = getPort(2222 + nodeIndex),
|
||||
val p2pPort: Int = getPort(12001 + (nodeIndex * 5)),
|
||||
val rpcPort: Int = getPort(12002 + (nodeIndex * 5)),
|
||||
val rpcProxy: Int = getPort(13002 + (nodeIndex * 5)),
|
||||
val rpcAdminPort: Int = getPort(12003 + (nodeIndex * 5)),
|
||||
val webPort: Int = getPort(12004 + (nodeIndex * 5)),
|
||||
val dbPort: Int = getPort(12005 + (nodeIndex * 5)),
|
||||
|
@ -0,0 +1,15 @@
|
||||
package net.corda.behave.node.configuration
|
||||
|
||||
class NetworkMapConfiguration(private val compatibilityZoneURL: String? = null) : ConfigurationTemplate() {
|
||||
|
||||
override val config: (Configuration) -> String
|
||||
get() = {
|
||||
if (compatibilityZoneURL != null) {
|
||||
"""
|
||||
|compatibilityZoneURL="$compatibilityZoneURL"
|
||||
"""
|
||||
} else {
|
||||
""
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@
|
||||
|
||||
package net.corda.behave.node.configuration
|
||||
|
||||
class NotaryConfiguration(private val notaryType: NotaryType = NotaryType.NONE) : ConfigurationTemplate() {
|
||||
class NotaryConfiguration(val notaryType: NotaryType = NotaryType.NONE) : ConfigurationTemplate() {
|
||||
|
||||
override val config: (Configuration) -> String
|
||||
get() = {
|
||||
|
@ -14,7 +14,7 @@ import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
|
||||
class JarCommand(
|
||||
jarFile: Path,
|
||||
val jarFile: Path,
|
||||
arguments: Array<out String>,
|
||||
directory: Path,
|
||||
timeout: Duration,
|
||||
|
@ -0,0 +1,36 @@
|
||||
package net.corda.behave.process
|
||||
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
|
||||
class JarCommandWithMain(
|
||||
jarFiles: List<Path>,
|
||||
mainClass: String,
|
||||
arguments: Array<String>,
|
||||
directory: Path,
|
||||
timeout: Duration,
|
||||
enableRemoteDebugging: Boolean = false
|
||||
) : Command(
|
||||
command = listOf(
|
||||
"/usr/bin/java",
|
||||
*extraArguments(enableRemoteDebugging),
|
||||
"-cp", "${jarFiles.joinToString(":")}",
|
||||
mainClass,
|
||||
*arguments
|
||||
),
|
||||
directory = directory,
|
||||
timeout = timeout
|
||||
) {
|
||||
|
||||
companion object {
|
||||
|
||||
private fun extraArguments(enableRemoteDebugging: Boolean) =
|
||||
if (enableRemoteDebugging) {
|
||||
arrayOf("-Dcapsule.jvm.args=-agentlib:jdwp=transport=dt_socket,server=y,suspend=y,address=5005")
|
||||
} else {
|
||||
arrayOf()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
|
||||
import net.corda.behave.database.DatabaseConnection
|
||||
import net.corda.behave.database.DatabaseType
|
||||
import net.corda.behave.database.configuration.SqlServerConfigurationTemplate
|
||||
import net.corda.behave.node.configuration.DatabaseConfiguration
|
||||
import net.corda.behave.service.ContainerService
|
||||
import net.corda.behave.service.ServiceSettings
|
||||
|
||||
class SqlServerService(
|
||||
name: String,
|
||||
port: Int,
|
||||
private val password: String,
|
||||
settings: ServiceSettings = ServiceSettings()
|
||||
) : ContainerService(name, port, "SQL Server is now ready for client connections", settings) {
|
||||
|
||||
override val baseImage = "microsoft/mssql-server-linux"
|
||||
|
||||
override val internalPort = 1433
|
||||
|
||||
init {
|
||||
addEnvironmentVariable("ACCEPT_EULA", "Y")
|
||||
addEnvironmentVariable("SA_PASSWORD", password)
|
||||
}
|
||||
|
||||
override fun verify(): Boolean {
|
||||
val config = DatabaseConfiguration(
|
||||
type = DatabaseType.SQL_SERVER,
|
||||
host = host,
|
||||
port = port,
|
||||
database = database,
|
||||
schema = schema,
|
||||
username = username,
|
||||
password = password
|
||||
)
|
||||
val connection = DatabaseConnection(config, SqlServerConfigurationTemplate())
|
||||
try {
|
||||
connection.use {
|
||||
return true
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
log.warn(ex.message, ex)
|
||||
ex.printStackTrace()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
val host = "localhost"
|
||||
val database = "master"
|
||||
val schema = "dbo"
|
||||
val username = "sa"
|
||||
val driver = "mssql-jdbc-6.2.2.jre8.jar"
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
minimumPlatformVersion = 1
|
||||
maxMessageSize = 10485760
|
||||
maxTransactionSize = 10485760
|
@ -0,0 +1,7 @@
|
||||
notaries : [{
|
||||
notaryNodeInfoFile: "../Notary/notary-node-info"
|
||||
validating: false
|
||||
}]
|
||||
minimumPlatformVersion = 1
|
||||
maxMessageSize = 10485760
|
||||
maxTransactionSize = 10485760
|
@ -0,0 +1,34 @@
|
||||
basedir = "."
|
||||
address = "localhost:1300"
|
||||
|
||||
#For local signing
|
||||
rootStorePath = ${basedir}"/certificates/rootstore.jks"
|
||||
keystorePath = ${basedir}"/certificates/caKeystore.jks"
|
||||
keystorePassword = "password"
|
||||
caPrivateKeyPassword = "password"
|
||||
rootKeystorePassword = "password"
|
||||
rootPrivateKeyPassword = "password"
|
||||
|
||||
# Database config
|
||||
dataSourceProperties {
|
||||
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
|
||||
"dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port}
|
||||
"dataSource.user" = sa
|
||||
"dataSource.password" = ""
|
||||
}
|
||||
database { runMigration = true }
|
||||
h2port = 0
|
||||
|
||||
# Doorman config
|
||||
# Comment out this section if running without doorman service
|
||||
doorman {
|
||||
approveInterval = 20
|
||||
approveAll = true
|
||||
}
|
||||
|
||||
# Network map config
|
||||
# Comment out this section if running without network map service
|
||||
#networkMap {
|
||||
# cacheTimeout = 1000
|
||||
# signInterval = 3000
|
||||
#}
|
32
experimental/behave/src/main/resources/doorman/node.conf
Normal file
32
experimental/behave/src/main/resources/doorman/node.conf
Normal file
@ -0,0 +1,32 @@
|
||||
basedir = "."
|
||||
address = "localhost:1300"
|
||||
|
||||
#For local signing
|
||||
rootStorePath = ${basedir}"/certificates/rootstore.jks"
|
||||
keystorePath = ${basedir}"/certificates/caKeystore.jks"
|
||||
keystorePassword = "password"
|
||||
caPrivateKeyPassword = "password"
|
||||
|
||||
# Database config
|
||||
dataSourceProperties {
|
||||
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
|
||||
"dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port}
|
||||
"dataSource.user" = sa
|
||||
"dataSource.password" = ""
|
||||
}
|
||||
database { runMigration = true }
|
||||
h2port = 0
|
||||
|
||||
# Doorman config
|
||||
# Comment out this section if running without doorman service
|
||||
doorman {
|
||||
approveInterval = 20
|
||||
approveAll = true
|
||||
}
|
||||
|
||||
# Network map config
|
||||
# Comment out this section if running without network map service
|
||||
networkMap {
|
||||
cacheTimeout = 1000
|
||||
signInterval = 3000
|
||||
}
|
@ -12,6 +12,7 @@ package net.corda.behave.scenarios
|
||||
|
||||
import cucumber.api.java.After
|
||||
import net.corda.behave.network.Network
|
||||
import net.corda.behave.node.Distribution
|
||||
import net.corda.behave.node.Node
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
@ -50,9 +51,15 @@ class ScenarioState {
|
||||
// Network is already running
|
||||
return
|
||||
}
|
||||
val networkBuilder = Network.new()
|
||||
|
||||
// Corda Network will be configured as R3 Corda (with Doorman/NMS) if any Node uses an R3.Corda distribution
|
||||
val r3CordaNode = nodes.find { it.networkType == Distribution.Type.R3_CORDA }
|
||||
val networkType = if (r3CordaNode != null) Distribution.Type.R3_CORDA else Distribution.Type.CORDA
|
||||
log.info("Corda network type: $networkType")
|
||||
|
||||
val networkBuilder = Network.new(networkType)
|
||||
for (node in nodes) {
|
||||
networkBuilder.addNode(node)
|
||||
networkBuilder.addNode(node.withNetworkType(networkType))
|
||||
}
|
||||
network = networkBuilder.generate()
|
||||
network?.start()
|
||||
@ -72,6 +79,14 @@ class ScenarioState {
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <T> withClientProxy(nodeName: String, crossinline action: (CordaRPCOps) -> T): T {
|
||||
withNetwork {
|
||||
return node(nodeName).http {
|
||||
action(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@After
|
||||
fun stopNetwork() {
|
||||
val network = network ?: return
|
||||
|
@ -30,4 +30,14 @@ abstract class Substeps(protected val state: ScenarioState) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
protected fun <T> withClientProxy(nodeName: String, action: ScenarioState.(CordaRPCOps) -> T): T {
|
||||
return state.withClientProxy(nodeName, {
|
||||
return@withClientProxy try {
|
||||
action(state, it)
|
||||
} catch (ex: Exception) {
|
||||
state.error<T>(ex.message ?: "Failed to execute HTTP call")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -39,6 +39,18 @@ class ConfigurationSteps : StepsBlock {
|
||||
?: error("Unknown notary type '$notaryType'"))
|
||||
}
|
||||
|
||||
Given<String, String>("^a node (\\w+) of version ([^ ]+) with proxy$") { name, version ->
|
||||
node(name).withRPCProxy(true)
|
||||
.withDistribution(Distribution.fromVersionString(version))
|
||||
}
|
||||
|
||||
Given<String, String, String>("^a (\\w+) notary node (\\w+) of version ([^ ]+) with proxy$") { notaryType, name, version ->
|
||||
node(name).withRPCProxy(true)
|
||||
.withDistribution(Distribution.fromVersionString(version))
|
||||
.withNotaryType(notaryType.toNotaryType()
|
||||
?: error("Unknown notary type '$notaryType'"))
|
||||
}
|
||||
|
||||
Given<String, String, String>("^a (\\w+) notary (\\w+) of version ([^ ]+)$") { type, name, version ->
|
||||
node(name)
|
||||
.withDistribution(Distribution.fromVersionString(version))
|
||||
@ -70,7 +82,5 @@ class ConfigurationSteps : StepsBlock {
|
||||
Given<String, String>("^node (\\w+) has app installed: (.+)$") { name, app ->
|
||||
node(name).withApp(app)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ Feature: Cash - Issuable Currencies
|
||||
|
||||
Examples:
|
||||
| Node-Version |
|
||||
| master |
|
||||
| r3-master |
|
||||
|
||||
Scenario Outline: Node has an issuable currency
|
||||
Given a node PartyA of version <Node-Version>
|
||||
@ -21,7 +21,7 @@ Feature: Cash - Issuable Currencies
|
||||
|
||||
Examples:
|
||||
| Node-Version |
|
||||
| master |
|
||||
| r3-master |
|
||||
|
||||
Scenario Outline: Node can issue a currency
|
||||
Given a node PartyA of version <Node-Version>
|
||||
@ -32,4 +32,4 @@ Feature: Cash - Issuable Currencies
|
||||
|
||||
Examples:
|
||||
| Node-Version |
|
||||
| master |
|
||||
| r3-master |
|
||||
|
@ -10,7 +10,8 @@ Feature: Database - Connection
|
||||
|
||||
Examples:
|
||||
| Node-Version | Database-Type |
|
||||
| master | H2 |
|
||||
| r3-master | H2 |
|
||||
|
||||
# To run this scenario using postgreSQL you must ensure that Docker is running locally
|
||||
# | master | postgreSQL |
|
||||
# To run this scenario using other DB providers you must ensure that Docker is running locally
|
||||
# | r3-master | postgreSQL |
|
||||
# | r3-master | SQL Server |
|
@ -12,7 +12,7 @@ Feature: Startup Information - Logging
|
||||
|
||||
Examples:
|
||||
| Node-Version | Database-Type |
|
||||
| master | H2 |
|
||||
| r3-master | H2 |
|
||||
|
||||
Scenario Outline: Node shows database details on startup
|
||||
Given a node PartyA of version <Node-Version>
|
||||
@ -22,7 +22,7 @@ Feature: Startup Information - Logging
|
||||
|
||||
Examples:
|
||||
| Node-Version | Database-Type |
|
||||
| master | H2 |
|
||||
| r3-master | H2 |
|
||||
|
||||
Scenario Outline: Node shows version information on startup
|
||||
Given a node PartyA of version <Node-Version>
|
||||
@ -30,5 +30,19 @@ Feature: Startup Information - Logging
|
||||
And node PartyA is on release version <Release-Version>
|
||||
|
||||
Examples:
|
||||
| Node-Version | Platform-Version | Release-Version |
|
||||
| master | 4 | corda-4.0-snapshot |
|
||||
| Node-Version | Platform-Version | Release-Version |
|
||||
| r3-master | 4 | R3.CORDA-3.0-SNAPSHOT |
|
||||
|
||||
Scenario Outline: Start-up a simple 3 node network with a non validating notary
|
||||
Given a node PartyA of version <Node1-Version>
|
||||
And a node PartyB of version <Node2-Version>
|
||||
And a node PartyC of version <Node3-Version>
|
||||
And a nonvalidating notary Notary of version <Notary-Version>
|
||||
When the network is ready
|
||||
Then user can retrieve logging information for node PartyA
|
||||
And user can retrieve logging information for node PartyB
|
||||
And user can retrieve logging information for node PartyC
|
||||
|
||||
Examples:
|
||||
| Node1-Version | Node2-Version | Node3-Version | Notary-Version |
|
||||
| r3-master | r3-master | r3-master | r3-master |
|
||||
|
@ -14,7 +14,7 @@ cd ${BUILD_DIR}
|
||||
../../gradlew behaveJar
|
||||
|
||||
BEHAVE_JAR=$(ls build/libs/corda-behave-*.jar | tail -n1)
|
||||
STAGING_ROOT=~/staging
|
||||
STAGING_ROOT=~/staging
|
||||
|
||||
# startup
|
||||
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/startup/logging.feature
|
||||
@ -23,4 +23,4 @@ java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.s
|
||||
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/cash/currencies.feature
|
||||
|
||||
# database
|
||||
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/cash/currencies.feature
|
||||
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/database/connection.feature
|
||||
|
@ -0,0 +1,44 @@
|
||||
package net.corda.behave.process
|
||||
|
||||
import net.corda.behave.node.Distribution
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.minutes
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import java.nio.file.Paths
|
||||
|
||||
class DBMigrationToolTests {
|
||||
|
||||
/**
|
||||
* Commands used to perform Database initialisation and migration as per:
|
||||
* http://docs.corda.r3.com/website/releases/docs_head/api-persistence.html#database-migration
|
||||
*/
|
||||
|
||||
// Set corresponding Java properties to point to valid Corda node configurations
|
||||
// eg. -DNODE_DIR=<location of node configuration directory> -DJDBC_DRIVER=postgresql-42.1.4.jar
|
||||
private val nodeRunDir = Paths.get(System.getProperty("NODE_DIR") ?: throw CordaRuntimeException("Please set NODE_DIR to point to valid Node configuration"))
|
||||
private val jdbcDriver = nodeRunDir / ".." / "libs" / (System.getProperty("JDBC_DRIVER") ?: throw CordaRuntimeException("Please set JDBC_DRIVER to point to valid JDBC driver jar file located under $nodeRunDir\\..\\libs"))
|
||||
|
||||
private val migrationToolMain = "com.r3.corda.dbmigration.DBMigration"
|
||||
|
||||
@Test
|
||||
fun `dry run migration`() {
|
||||
println(nodeRunDir)
|
||||
val command = JarCommandWithMain(listOf(Distribution.R3_MASTER.dbMigrationJar, jdbcDriver),
|
||||
migrationToolMain,
|
||||
arrayOf("--base-directory", "$nodeRunDir", "--dry-run"),
|
||||
nodeRunDir, 2.minutes)
|
||||
assertThat(command.run()).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `execute migration`() {
|
||||
println(nodeRunDir)
|
||||
val command = JarCommandWithMain(listOf(Distribution.R3_MASTER.dbMigrationJar, jdbcDriver),
|
||||
migrationToolMain,
|
||||
arrayOf("--base-directory", "$nodeRunDir", "--execute-migration"),
|
||||
nodeRunDir, 2.minutes)
|
||||
assertThat(command.run()).isEqualTo(0)
|
||||
}
|
||||
}
|
@ -0,0 +1,110 @@
|
||||
package net.corda.behave.process
|
||||
|
||||
import net.corda.behave.file.currentDirectory
|
||||
import net.corda.behave.file.doormanConfigDirectory
|
||||
import net.corda.behave.node.Distribution
|
||||
import net.corda.core.CordaRuntimeException
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.minutes
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import java.nio.file.Paths
|
||||
|
||||
class DoormanCommandTests {
|
||||
|
||||
/**
|
||||
* Commands that reproduce the setting up of an R3 Corda Network as per instructions in:
|
||||
* https://github.com/corda/enterprise/blob/master/network-management/README.md
|
||||
*/
|
||||
|
||||
private val source = doormanConfigDirectory
|
||||
private val doormanRunDir = currentDirectory / "build" / "runs" / "doorman"
|
||||
|
||||
// Set corresponding Java properties to point to valid Corda node configurations
|
||||
// eg. -DNOTARY_NODE_DIR=<location of notary node configuration directory>
|
||||
private val notaryRunDir = Paths.get(System.getProperty("NOTARY_NODE_DIR") ?: throw CordaRuntimeException("Please set NOTARY_NODE_DIR to point to valid Notary node configuration"))
|
||||
private val nodeRunDir = Paths.get(System.getProperty("NODE_DIR") ?: throw CordaRuntimeException("Please set NODE_DIR to point to valid Node configuration"))
|
||||
|
||||
@Test
|
||||
fun `step 1 - create key stores for local signer`() {
|
||||
println(doormanRunDir)
|
||||
source.toFile().copyRecursively(doormanRunDir.toFile(), true)
|
||||
|
||||
// ROOT
|
||||
val commandRoot = JarCommand(Distribution.R3_MASTER.doormanJar,
|
||||
arrayOf("--config-file", "$doormanConfigDirectory/node-init.conf", "--mode", "ROOT_KEYGEN", "--trust-store-password", "password"),
|
||||
doormanRunDir, 1.minutes)
|
||||
assertThat(commandRoot.run()).isEqualTo(0)
|
||||
|
||||
// CA
|
||||
val commandCA = JarCommand(Distribution.R3_MASTER.doormanJar,
|
||||
arrayOf("--config-file", "$doormanConfigDirectory/node-init.conf", "--mode", "CA_KEYGEN"),
|
||||
doormanRunDir, 1.minutes)
|
||||
assertThat(commandCA.run()).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `step 2 - start doorman service for notary registration`() {
|
||||
println(doormanRunDir)
|
||||
|
||||
val command = JarCommand(Distribution.R3_MASTER.doormanJar,
|
||||
arrayOf("--config-file", "$doormanConfigDirectory/node-init.conf"),
|
||||
doormanRunDir, 10.minutes)
|
||||
assertThat(command.run()).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `step 3 - create notary node and register with the doorman`() {
|
||||
println(notaryRunDir)
|
||||
val command = JarCommand(Distribution.R3_MASTER.cordaJar,
|
||||
arrayOf("--initial-registration",
|
||||
"--base-directory", "$notaryRunDir",
|
||||
"--network-root-truststore", "../doorman/certificates/distribute-nodes/network-root-truststore.jks",
|
||||
"--network-root-truststore-password", "password"),
|
||||
notaryRunDir, 2.minutes)
|
||||
assertThat(command.run()).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `step 4 - generate node info files for notary nodes`() {
|
||||
println(notaryRunDir)
|
||||
val command = JarCommand(Distribution.R3_MASTER.cordaJar,
|
||||
arrayOf("--just-generate-node-info",
|
||||
"--base-directory", "$notaryRunDir"),
|
||||
doormanRunDir, 1.minutes)
|
||||
assertThat(command.run()).isEqualTo(0)
|
||||
}
|
||||
|
||||
// step 5 Add notary identities to the network parameters
|
||||
// (already configured in template network parameters file)
|
||||
|
||||
@Test
|
||||
fun `step 6 - load initial network parameters file for network map service`() {
|
||||
println(doormanRunDir)
|
||||
val command = JarCommand(Distribution.R3_MASTER.doormanJar,
|
||||
arrayOf("--config-file", "$doormanRunDir/node.conf", "--set-network-parameters", "$doormanRunDir/network-parameters.conf"),
|
||||
doormanRunDir, 1.minutes)
|
||||
assertThat(command.run()).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `step 7 - Start a fully configured Doorman NMS`() {
|
||||
println(doormanRunDir)
|
||||
val command = JarCommand(Distribution.R3_MASTER.doormanJar,
|
||||
arrayOf("--config-file", "$doormanRunDir/node.conf"),
|
||||
doormanRunDir, 1.minutes)
|
||||
assertThat(command.run()).isEqualTo(0)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `step 8 - initial registration of network participant nodes`() {
|
||||
println(nodeRunDir)
|
||||
val command = JarCommand(Distribution.R3_MASTER.cordaJar,
|
||||
arrayOf("--initial-registration",
|
||||
"--network-root-truststore", "../doorman/certificates/distribute-nodes/network-root-truststore.jks",
|
||||
"--network-root-truststore-password", "password",
|
||||
"--base-directory", "$nodeRunDir"),
|
||||
doormanRunDir, 2.minutes)
|
||||
assertThat(command.run()).isEqualTo(0)
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package net.corda.behave.process
|
||||
|
||||
import net.corda.behave.file.tmpDirectory
|
||||
import net.corda.behave.node.Distribution
|
||||
import net.corda.core.internal.delete
|
||||
import net.corda.core.internal.div
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
import java.nio.file.Files
|
||||
|
||||
class RPCProxyCommandTests {
|
||||
|
||||
/**
|
||||
* Ensure you have configured the environment correctly by running the "prepare.sh" script
|
||||
* and then use a JVM option to ensure that STAGING_ROOT points to corresponding location used in the above script.
|
||||
* eg.
|
||||
* -ea -DSTAGING_ROOT=/home/staging
|
||||
*/
|
||||
|
||||
@Test
|
||||
fun `successful launch rpc proxy`() {
|
||||
val cordaDistribution = Distribution.MASTER.path
|
||||
val portNo = 13000
|
||||
val command = Command(listOf("$cordaDistribution/startRPCproxy.sh", "$cordaDistribution", "$portNo"))
|
||||
val exitCode = command.run()
|
||||
assertThat(exitCode).isEqualTo(0)
|
||||
|
||||
val pid = Files.lines(tmpDirectory / "rpcProxy-pid-$portNo").findFirst().get()
|
||||
println("Killing RPCProxyServer with pid: $pid")
|
||||
Command(listOf("kill", "-9", "$pid")).run()
|
||||
(tmpDirectory / "rpcProxy-pid-$portNo").delete()
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package net.corda.behave.service.proxy
|
||||
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.finance.POUNDS
|
||||
import net.corda.finance.flows.CashIssueFlow
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.Test
|
||||
|
||||
abstract class CordaRPCProxyClientTest {
|
||||
|
||||
companion object {
|
||||
private val RPC_PROXY_SERVER_PORT = 13002
|
||||
private val rpcProxyHostAndPort = NetworkHostAndPort("localhost", RPC_PROXY_SERVER_PORT)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun startFlowDynamic() {
|
||||
val rpcProxyClient = CordaRPCProxyClient(rpcProxyHostAndPort)
|
||||
val notary = rpcProxyClient.notaryIdentities()[0]
|
||||
val response = rpcProxyClient.startFlow(::CashIssueFlow, POUNDS(100), OpaqueBytes.of(1), notary).returnValue.getOrThrow()
|
||||
println(response)
|
||||
Assertions.assertThat(response.stx.toString()).matches("SignedTransaction\\(id=.*\\)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun nodeInfo() {
|
||||
val rpcProxyClient = CordaRPCProxyClient(rpcProxyHostAndPort)
|
||||
val response = rpcProxyClient.nodeInfo()
|
||||
println(response)
|
||||
Assertions.assertThat(response.toString()).matches("NodeInfo\\(addresses=\\[.*\\], legalIdentitiesAndCerts=\\[.*\\], platformVersion=.*, serial=.*\\)")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun notaryIdentities() {
|
||||
val rpcProxyClient = CordaRPCProxyClient(rpcProxyHostAndPort)
|
||||
val response = rpcProxyClient.notaryIdentities()
|
||||
println(response)
|
||||
Assertions.assertThat(response.first().name.toString()).isEqualTo("O=Notary, L=London, C=GB")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun registeredFlows() {
|
||||
val rpcProxyClient = CordaRPCProxyClient(rpcProxyHostAndPort)
|
||||
val response = rpcProxyClient.registeredFlows()
|
||||
println(response)
|
||||
// Node built-in flows
|
||||
Assertions.assertThat(response).contains("net.corda.core.flows.ContractUpgradeFlow\$Authorise",
|
||||
"net.corda.core.flows.ContractUpgradeFlow\$Deauthorise",
|
||||
"net.corda.core.flows.ContractUpgradeFlow\$Initiate")
|
||||
}
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
package net.corda.behave.service.proxy
|
||||
|
||||
import net.corda.behave.network.Network
|
||||
import net.corda.behave.node.configuration.NotaryType
|
||||
import org.junit.AfterClass
|
||||
import org.junit.BeforeClass
|
||||
|
||||
class OSNetworkTest : CordaRPCProxyClientTest() {
|
||||
|
||||
/**
|
||||
* Ensure you have configured the environment correctly by running the "prepare.sh" script
|
||||
* and then use a JVM option to ensure that STAGING_ROOT points to corresponding location used in the above script.
|
||||
* eg.
|
||||
* -ea -DSTAGING_ROOT=/home/staging
|
||||
*
|
||||
* Use -DDISABLE_CLEANUP=true to prevent deletion of the run-time configuration directories.
|
||||
*/
|
||||
|
||||
companion object {
|
||||
private lateinit var network : Network
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic fun setUp() {
|
||||
network = Network.new().addNode(name = "Notary", notaryType = NotaryType.NON_VALIDATING, withRPCProxy = true).generate()
|
||||
network.start()
|
||||
network.waitUntilRunning()
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@JvmStatic fun tearDown() {
|
||||
network.stop()
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
package net.corda.behave.service.proxy
|
||||
|
||||
import net.corda.behave.network.Network
|
||||
import net.corda.behave.node.Distribution.Companion.R3_MASTER
|
||||
import net.corda.behave.node.Distribution.Type.R3_CORDA
|
||||
import net.corda.behave.node.configuration.NotaryType
|
||||
import org.junit.AfterClass
|
||||
import org.junit.BeforeClass
|
||||
|
||||
class R3CordaNetworkTest : CordaRPCProxyClientTest() {
|
||||
|
||||
/**
|
||||
* Ensure you have configured the environment correctly by running the "prepare.sh" script
|
||||
* and then use a JVM option to ensure that STAGING_ROOT points to corresponding location used in the above script.
|
||||
* eg.
|
||||
* -ea -DSTAGING_ROOT=/home/staging
|
||||
*
|
||||
* Use -DDISABLE_CLEANUP=true to prevent deletion of the run-time configuration directories.
|
||||
*/
|
||||
|
||||
companion object {
|
||||
private lateinit var network : Network
|
||||
|
||||
@BeforeClass
|
||||
@JvmStatic fun setUp() {
|
||||
network = Network.new(R3_CORDA).addNode(distribution = R3_MASTER, name = "Notary", notaryType = NotaryType.NON_VALIDATING, withRPCProxy = true).generate()
|
||||
network.start()
|
||||
network.waitUntilRunning()
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
@JvmStatic fun tearDown() {
|
||||
network.stop()
|
||||
}
|
||||
}
|
||||
}
|
@ -11,13 +11,79 @@
|
||||
package net.corda.behave.network
|
||||
|
||||
import net.corda.behave.database.DatabaseType
|
||||
import net.corda.behave.node.Distribution
|
||||
import net.corda.behave.node.configuration.NotaryType
|
||||
import net.corda.core.utilities.hours
|
||||
import net.corda.core.utilities.seconds
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
import java.time.Duration
|
||||
|
||||
class NetworkTests {
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun `OS Corda network of single node with RPC proxy can be spun up`() {
|
||||
val distribution = Distribution.MASTER
|
||||
val network = Network
|
||||
.new()
|
||||
.addNode(name = "Foo", distribution = distribution, notaryType = NotaryType.NON_VALIDATING, withRPCProxy = true)
|
||||
.generate()
|
||||
network.use {
|
||||
it.waitUntilRunning(300.seconds)
|
||||
it.keepAlive(300.seconds)
|
||||
it.signal()
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun `R3 Corda network of single node with RPC proxy can be spun up`() {
|
||||
val network = Network
|
||||
.new(Distribution.Type.R3_CORDA)
|
||||
.addNode(name = "Notary", distribution = Distribution.R3_MASTER, notaryType = NotaryType.NON_VALIDATING, withRPCProxy = true)
|
||||
.generate()
|
||||
network.use {
|
||||
it.waitUntilRunning(1.hours)
|
||||
it.keepAlive(1.hours)
|
||||
it.signal()
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun `Mixed OS Corda network of two nodes (with an RPC proxy each) and a non-validating notary can be spun up`() {
|
||||
// Note: this test exercises the NetworkBootstrapper to setup a local network
|
||||
val distribution = Distribution.MASTER
|
||||
val network = Network
|
||||
.new()
|
||||
.addNode(name = "EntityA", distribution = Distribution.MASTER, withRPCProxy = true)
|
||||
.addNode(name = "EntityB", distribution = Distribution.R3_MASTER, withRPCProxy = true)
|
||||
.addNode(name = "Notary", distribution = distribution, notaryType = NotaryType.NON_VALIDATING)
|
||||
.generate()
|
||||
network.use {
|
||||
it.waitUntilRunning(Duration.ofDays(1))
|
||||
it.keepAlive(Duration.ofDays(1))
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun `Mixed R3 Corda network of two nodes (with an RPC proxy each) and a non-validating notary can be spun up`() {
|
||||
// Note: this test exercises the Doorman / Notary / NMS setup sequence
|
||||
val distribution = Distribution.R3_MASTER
|
||||
val network = Network
|
||||
.new()
|
||||
.addNode(name = "EntityA", distribution = Distribution.R3_MASTER, withRPCProxy = true)
|
||||
.addNode(name = "EntityB", distribution = Distribution.MASTER, withRPCProxy = true)
|
||||
.addNode(name = "Notary", distribution = distribution, notaryType = NotaryType.NON_VALIDATING)
|
||||
.generate()
|
||||
network.use {
|
||||
it.waitUntilRunning(Duration.ofDays(1))
|
||||
it.keepAlive(Duration.ofDays(1))
|
||||
}
|
||||
}
|
||||
|
||||
@Ignore
|
||||
@Test
|
||||
fun `network of two nodes can be spun up`() {
|
||||
|
@ -31,7 +31,7 @@ class CommandTests {
|
||||
@Test
|
||||
fun `output stream for command can be observed`() {
|
||||
val subscriber = TestSubscriber<String>()
|
||||
val exitCode = Command(listOf("ls", "/")).use(subscriber) { _, output ->
|
||||
val exitCode = Command(listOf("ls", "/")).use(subscriber) { _, _ ->
|
||||
subscriber.awaitTerminalEvent()
|
||||
subscriber.assertCompleted()
|
||||
subscriber.assertNoErrors()
|
||||
|
@ -23,15 +23,15 @@ import java.util.*
|
||||
class CashSelectionOracleImpl : AbstractCashSelection(maxRetries = 16, retrySleep = 1000, retryCap = 5000) {
|
||||
|
||||
companion object {
|
||||
val JDBC_DRIVER_NAME = "Oracle JDBC driver"
|
||||
const val JDBC_DRIVER_NAME = "Oracle JDBC driver"
|
||||
private val log = contextLogger()
|
||||
}
|
||||
|
||||
override fun isCompatible(metaData: DatabaseMetaData): Boolean {
|
||||
return metaData.driverName == JDBC_DRIVER_NAME
|
||||
return metaData.driverName.startsWith(JDBC_DRIVER_NAME, ignoreCase = true)
|
||||
}
|
||||
|
||||
override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME"
|
||||
override fun toString() = "${this::class.qualifiedName} for '$JDBC_DRIVER_NAME'"
|
||||
|
||||
override fun executeQuery(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?,
|
||||
onlyFromIssuerParties: Set<AbstractParty>, withIssuerRefs: Set<OpaqueBytes>, withResultSet: (ResultSet) -> Boolean): Boolean {
|
||||
|
@ -28,7 +28,7 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() {
|
||||
}
|
||||
|
||||
override fun isCompatible(metadata: DatabaseMetaData): Boolean {
|
||||
return metadata.driverName == JDBC_DRIVER_NAME
|
||||
return metadata.driverName.startsWith(JDBC_DRIVER_NAME, ignoreCase = true)
|
||||
}
|
||||
|
||||
override fun toString() = "${this::class.qualifiedName} for '$JDBC_DRIVER_NAME'"
|
||||
|
@ -28,15 +28,15 @@ import java.util.*
|
||||
class CashSelectionSQLServerImpl : AbstractCashSelection(maxRetries = 16, retrySleep = 1000, retryCap = 5000) {
|
||||
|
||||
companion object {
|
||||
val JDBC_DRIVER_NAME = "Microsoft JDBC Driver 6.2 for SQL Server"
|
||||
const val JDBC_DRIVER_NAME = "Microsoft JDBC Driver"
|
||||
private val log = contextLogger()
|
||||
}
|
||||
|
||||
override fun isCompatible(metaData: DatabaseMetaData): Boolean {
|
||||
return metaData.driverName == JDBC_DRIVER_NAME
|
||||
return metaData.driverName.startsWith(JDBC_DRIVER_NAME, ignoreCase = true)
|
||||
}
|
||||
|
||||
override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME"
|
||||
override fun toString() = "${this::class.qualifiedName} for '$JDBC_DRIVER_NAME'"
|
||||
|
||||
override fun executeQuery(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?,
|
||||
onlyFromIssuerParties: Set<AbstractParty>,
|
||||
|
@ -32,11 +32,24 @@ sourceSets {
|
||||
srcDir file('../testing/test-utils/src/main/resources')
|
||||
}
|
||||
}
|
||||
scenarioTest {
|
||||
kotlin {
|
||||
compileClasspath += main.output + test.output
|
||||
runtimeClasspath += main.output + test.output
|
||||
srcDir file('src/system-test/scenario/kotlin')
|
||||
}
|
||||
resources {
|
||||
srcDir 'src/system-test/scenario/resources'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations {
|
||||
integrationTestCompile.extendsFrom testCompile
|
||||
integrationTestRuntime.extendsFrom testRuntime
|
||||
|
||||
scenarioTestCompile.extendsFrom testCompile
|
||||
scenarioTestRuntime.extendsFrom testRuntime
|
||||
}
|
||||
|
||||
dependencies {
|
||||
@ -69,6 +82,7 @@ dependencies {
|
||||
|
||||
// Test dependencies
|
||||
testCompile project(':node-driver')
|
||||
scenarioTestCompile project(path: ":experimental:behave", configuration: 'testArtifacts')
|
||||
testCompile "junit:junit:$junit_version"
|
||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||
}
|
||||
@ -147,6 +161,16 @@ task integrationTest(type: Test, dependsOn: []) {
|
||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
||||
}
|
||||
|
||||
task scenarioTest(type: Test, dependsOn: []) {
|
||||
testClassesDirs = sourceSets.scenarioTest.output.classesDirs
|
||||
classpath = sourceSets.scenarioTest.runtimeClasspath
|
||||
}
|
||||
|
||||
task scenarioJar(type: Jar, dependsOn: classes) {
|
||||
classifier "behave-test"
|
||||
from sourceSets.scenarioTest.output
|
||||
}
|
||||
|
||||
publishing {
|
||||
publications {
|
||||
simmvaluationdemo(MavenPublication) {
|
||||
@ -155,6 +179,7 @@ publishing {
|
||||
|
||||
artifact sourceJar
|
||||
artifact javadocJar
|
||||
artifact scenarioJar
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,14 @@
|
||||
package net.corda.vega.scenarios
|
||||
|
||||
import net.corda.behave.scenarios.api.StepsBlock
|
||||
import net.corda.behave.scenarios.api.StepsProvider
|
||||
import net.corda.vega.scenarios.steps.SimmValuationSteps
|
||||
|
||||
class SIMMValuationStepsProvider : StepsProvider {
|
||||
|
||||
override val name: String
|
||||
get() = SIMMValuationStepsProvider::javaClass.name
|
||||
|
||||
override val stepsDefinition: StepsBlock
|
||||
get() = SimmValuationSteps()
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package net.corda.vega.scenarios.helpers
|
||||
|
||||
import com.opengamma.strata.product.common.BuySell
|
||||
import net.corda.behave.scenarios.ScenarioState
|
||||
import net.corda.behave.scenarios.helpers.Substeps
|
||||
import net.corda.core.messaging.startFlow
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.vega.api.SwapDataModel
|
||||
import net.corda.vega.flows.IRSTradeFlow
|
||||
import java.math.BigDecimal
|
||||
import java.time.LocalDate
|
||||
|
||||
class SIMMValuation(state: ScenarioState) : Substeps(state) {
|
||||
|
||||
private companion object {
|
||||
// SIMM demo can only currently handle one valuation date due to a lack of market data or a market data source.
|
||||
val valuationDate: LocalDate = LocalDate.parse("2016-06-06")
|
||||
val tradeId = "trade1"
|
||||
}
|
||||
|
||||
fun trade(ownNode: String, counterpartyNode: String) : SignedTransaction {
|
||||
return withClientProxy(ownNode) {
|
||||
val swap = SwapDataModel(tradeId, "desc", valuationDate, "EUR_FIXED_1Y_EURIBOR_3M",
|
||||
valuationDate, LocalDate.parse("2020-01-02"), BuySell.BUY, BigDecimal.valueOf(1000), BigDecimal.valueOf(0.1))
|
||||
|
||||
val ownParty = it.partiesFromName(ownNode, false).first()
|
||||
val counterParty = it.partiesFromName(counterpartyNode, false).first()
|
||||
|
||||
val buyer = if (swap.buySell.isBuy) ownParty else counterParty
|
||||
val seller = if (swap.buySell.isSell) ownParty else counterParty
|
||||
return@withClientProxy it.startFlow(IRSTradeFlow::Requester, swap.toData(buyer, seller), ownParty).returnValue.getOrThrow()
|
||||
}
|
||||
}
|
||||
|
||||
fun runValuation(node: String) {
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
fun checkValuation(value: Long) {
|
||||
TODO("not implemented")
|
||||
}
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package net.corda.vega.scenarios.steps
|
||||
|
||||
import net.corda.behave.scenarios.ScenarioState
|
||||
import net.corda.behave.scenarios.api.StepsBlock
|
||||
import net.corda.vega.scenarios.helpers.SIMMValuation
|
||||
|
||||
class SimmValuationSteps : StepsBlock {
|
||||
|
||||
override fun initialize(state: ScenarioState) {
|
||||
val simmValuation = SIMMValuation(state)
|
||||
|
||||
Then<String, String>("^node (\\w+) can trade with node (\\w+)$") { nodeA, nodeB ->
|
||||
state.withNetwork {
|
||||
simmValuation.trade(nodeA, nodeB)
|
||||
}
|
||||
}
|
||||
|
||||
Then<String>("^node (\\w+) can run portfolio valuation$") { node ->
|
||||
state.withNetwork {
|
||||
simmValuation.runValuation(node)
|
||||
}
|
||||
}
|
||||
|
||||
Then<String, Long>("^node (\\w+) portfolio valuation is (\\d+)$") { _, value ->
|
||||
state.withNetwork {
|
||||
simmValuation.checkValuation(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,63 @@
|
||||
@qa compatibility @node @cordapps
|
||||
Feature: Compatibility - Corda distributions (OS and Enterprise) running different CorDapps
|
||||
To support an interoperable Corda network, different CorDapps must have the ability to transact in mixed Corda (OS) and R3 Corda (Enterprise) networks.
|
||||
|
||||
Scenario Outline: Run the SIMM valuation demo in a Corda OS Network.
|
||||
Given a node PartyA of version <Corda-Node-Version> with proxy
|
||||
And node PartyA has app installed: <Cordapp-Name>
|
||||
And a node PartyB of version <Corda-Node-Version>
|
||||
And node PartyB has app installed: <Cordapp-Name>
|
||||
And a nonvalidating notary Notary of version <Corda-Node-Version>
|
||||
When the network is ready
|
||||
And node PartyA has loaded app <Cordapp-Name>
|
||||
And node PartyB has loaded app <Cordapp-Name>
|
||||
Then node PartyA can trade with node PartyB
|
||||
And node PartyA vault contains 1 states
|
||||
And node PartyB vault contains 1 states
|
||||
And node PartyA can run portfolio valuation
|
||||
And node PartyA portfolio valuation is <Valuation>
|
||||
And node PartyB portfolio valuation is <Valuation>
|
||||
|
||||
Examples:
|
||||
| Corda-Node-Version | Cordapp-Name | Valuation |
|
||||
| corda-3.0 | simm-valuation-demo | 12345 |
|
||||
|
||||
Scenario Outline: Run the SIMM valuation demo in an R3 Corda Network.
|
||||
Given a node PartyA of version <R3-Corda-Node-Version> with proxy
|
||||
And node PartyA has app installed: <Cordapp-Name>
|
||||
And a node PartyB of version <R3-Corda-Node-Version>
|
||||
And node PartyB has app installed: <Cordapp-Name>
|
||||
And a nonvalidating notary Notary of version <Corda-Node-Version>
|
||||
When the network is ready
|
||||
And node PartyA has loaded app <Cordapp-Name>
|
||||
And node PartyB has loaded app <Cordapp-Name>
|
||||
Then node PartyA can trade with node PartyB
|
||||
And node PartyA vault contains 1 states
|
||||
And node PartyB vault contains 1 states
|
||||
And node PartyA can run portfolio valuation
|
||||
And node PartyA portfolio valuation is <Valuation>
|
||||
And node PartyB portfolio valuation is <Valuation>
|
||||
|
||||
Examples:
|
||||
| R3-Corda-Node-Version | Cordapp-Name | Valuation |
|
||||
| R3.CORDA-3.0.0-DEV-PREVIEW-3 | simm-valuation-demo | 12345 |
|
||||
|
||||
Scenario Outline: Corda (OS) Node can transact with R3 Corda (Enterprise) node using the SIMM valuation demo.
|
||||
Given a node PartyA of version <Corda-Node-Version> with proxy
|
||||
And node PartyA has app installed: <Cordapp-Name>
|
||||
And a node PartyB of version <R3-Corda-Node-Version>
|
||||
And node PartyB has app installed: <Cordapp-Name>
|
||||
And a nonvalidating notary Notary of version <Corda-Node-Version>
|
||||
When the network is ready
|
||||
And node PartyA has loaded app <Cordapp-Name>
|
||||
And node PartyB has loaded app <Cordapp-Name>
|
||||
Then node PartyA can trade with node PartyB
|
||||
And node PartyA vault contains 1 states
|
||||
And node PartyB vault contains 1 states
|
||||
And node PartyA can run portfolio valuation
|
||||
And node PartyA portfolio valuation is <Valuation>
|
||||
And node PartyB portfolio valuation is <Valuation>
|
||||
|
||||
Examples:
|
||||
| Corda-Node-Version | R3-Corda-Node-Version | Cordapp-Name | Valuation |
|
||||
| corda-3.0 | R3.CORDA-3.0.0-DEV-PREVIEW-3 | simm-valuation-demo | 12345 |
|
@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
#
|
||||
# Run this script from the sample directory
|
||||
#
|
||||
# $ pwd
|
||||
# ./IdeaProjects/corda-reviews/samples/simm-valuation-demo
|
||||
# $ src/system-test/scenario/resources/scripts/run-behave-simm-valuation.sh
|
||||
#
|
||||
#
|
||||
|
||||
CURRENT_DIR=$PWD
|
||||
|
||||
BEHAVE_DIR=$CURRENT_DIR/../../experimental/behave
|
||||
cd $BEHAVE_DIR
|
||||
../../gradlew behaveJar
|
||||
|
||||
DEMO_DIR=$CURRENT_DIR
|
||||
cd $DEMO_DIR
|
||||
../../gradlew scenarioJar
|
||||
|
||||
echo $BEHAVE_DIR
|
||||
java -cp "$BEHAVE_DIR/build/libs/corda-behave.jar:$CURRENT_DIR/build/libs/simm-valuation-demo-behave-test.jar" net.corda.behave.scenarios.ScenarioRunner --glue "net.corda.behave.scenarios" -path ./src/system-test/scenario/resources/features/simm-valuation.feature -d
|
||||
# -d to perform dry-run
|
@ -13,4 +13,4 @@ cd ${BEHAVE_DIR}
|
||||
../../gradlew behaveJar
|
||||
|
||||
# QA interoperability
|
||||
java -jar ${BEHAVE_DIR}/build/libs/corda-behave.jar -d --glue net.corda.behave.scenarios -path ${R3CORDA_HOME}/testing/qa/behave/compatibility/resources/features/qa-interop-testing.feature
|
||||
java -jar ${BEHAVE_DIR}/build/libs/corda-behave.jar -d --glue net.corda.behave.scenarios -path ${R3CORDA_HOME}/testing/qa/behave/compatibility/resources/features/interoperability.feature
|
||||
|
@ -14,4 +14,4 @@ cd ${BEHAVE_DIR}
|
||||
../../gradlew behaveJar
|
||||
|
||||
# QA functional
|
||||
java -jar ${BEHAVE_DIR}/build/libs/corda-behave.jar -d --glue net.corda.behave.scenarios -path ${R3CORDA_HOME}/testing/qa/behave/functional/resources/features/qa-functional-testing.feature
|
||||
java -jar ${BEHAVE_DIR}/build/libs/corda-behave.jar -d --glue net.corda.behave.scenarios -path ${R3CORDA_HOME}/testing/qa/behave/functional/resources/features/functional.feature
|
||||
|
Loading…
x
Reference in New Issue
Block a user