mirror of
https://github.com/corda/corda.git
synced 2025-02-14 14:42:32 +00:00
Extensions to Corda Behave for CTS.
Minor fixes following integration and regression testing with CordaRPCProxy Remove unrelated code. Address remaining PR review comments.
This commit is contained in:
parent
a5e2058a5b
commit
8f47fe8c68
8
.idea/compiler.xml
generated
8
.idea/compiler.xml
generated
@ -10,9 +10,11 @@
|
|||||||
<module name="bank-of-corda-demo_integrationTest" target="1.8" />
|
<module name="bank-of-corda-demo_integrationTest" target="1.8" />
|
||||||
<module name="bank-of-corda-demo_main" target="1.8" />
|
<module name="bank-of-corda-demo_main" target="1.8" />
|
||||||
<module name="bank-of-corda-demo_test" target="1.8" />
|
<module name="bank-of-corda-demo_test" target="1.8" />
|
||||||
|
<module name="behave_api" target="1.8" />
|
||||||
<module name="behave_behave" target="1.8" />
|
<module name="behave_behave" target="1.8" />
|
||||||
<module name="behave_main" target="1.8" />
|
<module name="behave_main" target="1.8" />
|
||||||
<module name="behave_scenario" 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="behave_test" target="1.8" />
|
||||||
<module name="bootstrapper_main" target="1.8" />
|
<module name="bootstrapper_main" target="1.8" />
|
||||||
<module name="bootstrapper_test" target="1.8" />
|
<module name="bootstrapper_test" target="1.8" />
|
||||||
@ -64,6 +66,10 @@
|
|||||||
<module name="example-code_integrationTest" target="1.8" />
|
<module name="example-code_integrationTest" target="1.8" />
|
||||||
<module name="example-code_main" target="1.8" />
|
<module name="example-code_main" target="1.8" />
|
||||||
<module name="example-code_test" target="1.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_test" target="1.8" />
|
||||||
<module name="experimental-kryo-hook_main" target="1.8" />
|
<module name="experimental-kryo-hook_main" target="1.8" />
|
||||||
<module name="experimental-kryo-hook_test" target="1.8" />
|
<module name="experimental-kryo-hook_test" target="1.8" />
|
||||||
<module name="experimental_main" target="1.8" />
|
<module name="experimental_main" target="1.8" />
|
||||||
@ -151,6 +157,8 @@
|
|||||||
<module name="shell_test" target="1.8" />
|
<module name="shell_test" target="1.8" />
|
||||||
<module name="simm-valuation-demo_integrationTest" 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_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="simm-valuation-demo_test" target="1.8" />
|
||||||
<module name="smoke-test-utils_main" target="1.8" />
|
<module name="smoke-test-utils_main" target="1.8" />
|
||||||
<module name="smoke-test-utils_test" target="1.8" />
|
<module name="smoke-test-utils_test" target="1.8" />
|
||||||
|
@ -16,6 +16,7 @@ buildscript {
|
|||||||
ext.docker_client_version = '8.11.0'
|
ext.docker_client_version = '8.11.0'
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
maven {
|
maven {
|
||||||
jcenter()
|
jcenter()
|
||||||
url 'https://jitpack.io'
|
url 'https://jitpack.io'
|
||||||
@ -31,10 +32,12 @@ group 'net.corda.behave'
|
|||||||
|
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'kotlin'
|
apply plugin: 'kotlin'
|
||||||
|
apply plugin: 'net.corda.plugins.publish-utils'
|
||||||
|
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = 1.8
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
|
mavenLocal()
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,15 +46,42 @@ sourceSets {
|
|||||||
java {
|
java {
|
||||||
compileClasspath += main.output
|
compileClasspath += main.output
|
||||||
runtimeClasspath += main.output
|
runtimeClasspath += main.output
|
||||||
srcDirs = ["src/main/kotlin", "src/scenario/kotlin"]
|
srcDirs = ['src/main/kotlin', 'src/scenario/kotlin']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
scenario {
|
||||||
|
java {
|
||||||
|
compileClasspath += main.output
|
||||||
|
runtimeClasspath += main.output
|
||||||
|
srcDir file('src/scenario/kotlin')
|
||||||
|
}
|
||||||
|
resources {
|
||||||
|
srcDirs = ['src/scenario/resources']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
smokeTest {
|
||||||
|
kotlin {
|
||||||
|
compileClasspath += main.output
|
||||||
|
runtimeClasspath += main.output
|
||||||
|
srcDir file('src/smoke-test/kotlin')
|
||||||
|
}
|
||||||
|
resources {
|
||||||
|
srcDirs = ['src/scenario/resources']
|
||||||
}
|
}
|
||||||
resources.srcDir file('src/scenario/resources')
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
configurations {
|
configurations {
|
||||||
behaveCompile.extendsFrom testCompile
|
scenarioCompile.extendsFrom testCompile
|
||||||
behaveRuntime.extendsFrom testRuntime
|
scenarioRuntime.extendsFrom testRuntime
|
||||||
|
|
||||||
|
behaveCompile.extendsFrom scenarioCompile
|
||||||
|
behaveRuntime.extendsFrom scenarioRuntime
|
||||||
|
|
||||||
|
smokeTestCompile.extendsFrom compile
|
||||||
|
smokeTestRuntime.extendsFrom runtime
|
||||||
|
|
||||||
|
testArtifacts.extendsFrom behaveRuntime
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -91,16 +121,24 @@ dependencies {
|
|||||||
compile project(':node-api')
|
compile project(':node-api')
|
||||||
compile project(':client:rpc')
|
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 "junit:junit:$junit_version"
|
||||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
testCompile "org.assertj:assertj-core:$assertj_version"
|
||||||
|
|
||||||
// Scenarios / End-to-End Tests
|
// Scenarios / End-to-End Tests
|
||||||
|
scenarioCompile "info.cukes:cucumber-java8:$cucumber_version"
|
||||||
|
scenarioCompile "info.cukes:cucumber-junit:$cucumber_version"
|
||||||
|
scenarioCompile "info.cukes:cucumber-picocontainer:$cucumber_version"
|
||||||
|
|
||||||
behaveCompile "info.cukes:cucumber-java8:$cucumber_version"
|
// Smoke tests
|
||||||
behaveCompile "info.cukes:cucumber-junit:$cucumber_version"
|
smokeTestCompile project(':smoke-test-utils')
|
||||||
behaveCompile "info.cukes:cucumber-picocontainer:$cucumber_version"
|
smokeTestCompile "org.assertj:assertj-core:${assertj_version}"
|
||||||
|
smokeTestCompile "junit:junit:$junit_version"
|
||||||
}
|
}
|
||||||
|
|
||||||
compileKotlin {
|
compileKotlin {
|
||||||
@ -115,6 +153,11 @@ test {
|
|||||||
testLogging.showStandardStreams = true
|
testLogging.showStandardStreams = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
task smokeTest(type: Test) {
|
||||||
|
testClassesDirs = sourceSets.smokeTest.output.classesDirs
|
||||||
|
classpath = sourceSets.smokeTest.runtimeClasspath
|
||||||
|
}
|
||||||
|
|
||||||
task behaveJar(type: Jar) {
|
task behaveJar(type: Jar) {
|
||||||
baseName "corda-behave"
|
baseName "corda-behave"
|
||||||
from sourceSets.behave.output
|
from sourceSets.behave.output
|
||||||
@ -133,3 +176,24 @@ task behaveJar(type: Jar) {
|
|||||||
attributes 'Main-Class': 'net.corda.behave.scenarios.ScenarioRunner'
|
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:
|
# For example:
|
||||||
# corda-master => git clone https://github.com/corda/corda
|
# corda-master => git clone https://github.com/corda/corda
|
||||||
# r3corda-master => git clone https://github.com/corda/enterprise
|
# r3corda-master => git clone https://github.com/corda/enterprise
|
||||||
VERSION=corda-master
|
VERSION=r3corda-master
|
||||||
STAGING_DIR=~/staging
|
STAGING_DIR=~/staging
|
||||||
CORDA_DIR=${STAGING_DIR}/corda/${VERSION}
|
CORDA_DIR=${STAGING_DIR}/corda/${VERSION}
|
||||||
CORDAPP_DIR=${CORDA_DIR}/apps
|
CORDAPP_DIR=${CORDA_DIR}/apps
|
||||||
@ -18,18 +18,32 @@ mkdir -p ${CORDA_DIR}
|
|||||||
mkdir -p ${CORDAPP_DIR}
|
mkdir -p ${CORDAPP_DIR}
|
||||||
mkdir -p ${DRIVERS_DIR}
|
mkdir -p ${DRIVERS_DIR}
|
||||||
|
|
||||||
# Copy Corda capsule into deps
|
# Copy Corda capsule into staging
|
||||||
cd ../..
|
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
|
cp -v $(ls node/capsule/build/libs/corda-*.jar | tail -n1) ${CORDA_DIR}/corda.jar
|
||||||
|
|
||||||
# Copy finance library
|
# Copy finance library
|
||||||
cp -v $(ls finance/build/libs/corda-finance-*.jar | tail -n1) ${CORDAPP_DIR}
|
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
|
# 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 "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 "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
|
# Build Network Bootstrapper
|
||||||
./gradlew buildBootstrapperJar
|
./gradlew buildBootstrapperJar
|
||||||
cp -v $(ls tools/bootstrapper/build/libs/*.jar | tail -n1) ${CORDA_DIR}/network-bootstrapper.jar
|
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.H2ConfigurationTemplate
|
||||||
import net.corda.behave.database.configuration.PostgresConfigurationTemplate
|
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.Configuration
|
||||||
import net.corda.behave.node.configuration.DatabaseConfiguration
|
import net.corda.behave.node.configuration.DatabaseConfiguration
|
||||||
import net.corda.behave.service.database.H2Service
|
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()
|
POSTGRES(DatabaseSettings()
|
||||||
.withDatabase(PostgreSQLService.database)
|
|
||||||
.withDriver(PostgreSQLService.driver)
|
.withDriver(PostgreSQLService.driver)
|
||||||
.withSchema(PostgreSQLService.schema)
|
.withSchema(PostgreSQLService.schema)
|
||||||
.withUser(PostgreSQLService.username)
|
.withUser(PostgreSQLService.username)
|
||||||
@ -48,8 +59,9 @@ enum class DatabaseType(val settings: DatabaseSettings) {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
fun fromName(name: String): DatabaseType? = when (name.toLowerCase()) {
|
fun fromName(name: String): DatabaseType? = when (name.replace("[ _-]".toRegex(), "").toLowerCase()) {
|
||||||
"h2" -> H2
|
"h2" -> H2
|
||||||
|
"sqlserver" -> SQL_SERVER
|
||||||
"postgres" -> POSTGRES
|
"postgres" -> POSTGRES
|
||||||
"postgresql" -> POSTGRES
|
"postgresql" -> POSTGRES
|
||||||
else -> null
|
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
|
package net.corda.behave.file
|
||||||
|
|
||||||
|
import net.corda.core.internal.div
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import java.nio.file.Paths
|
import java.nio.file.Paths
|
||||||
|
|
||||||
@ -19,3 +20,9 @@ val currentDirectory: Path
|
|||||||
// location of Corda distributions and Drivers dependencies
|
// location of Corda distributions and Drivers dependencies
|
||||||
val stagingRoot: Path
|
val stagingRoot: Path
|
||||||
get() = System.getProperty("STAGING_ROOT")?.let { Paths.get(it) } ?: currentDirectory
|
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,24 @@
|
|||||||
package net.corda.behave.network
|
package net.corda.behave.network
|
||||||
|
|
||||||
import net.corda.behave.database.DatabaseType
|
import net.corda.behave.database.DatabaseType
|
||||||
import net.corda.behave.file.LogSource
|
import net.corda.behave.file.*
|
||||||
import net.corda.behave.file.currentDirectory
|
import net.corda.behave.monitoring.PatternWatch
|
||||||
import net.corda.behave.file.stagingRoot
|
|
||||||
import net.corda.behave.node.Distribution
|
import net.corda.behave.node.Distribution
|
||||||
import net.corda.behave.node.Node
|
import net.corda.behave.node.Node
|
||||||
import net.corda.behave.node.configuration.NotaryType
|
import net.corda.behave.node.configuration.NotaryType
|
||||||
|
import net.corda.behave.process.Command
|
||||||
import net.corda.behave.process.JarCommand
|
import net.corda.behave.process.JarCommand
|
||||||
import net.corda.core.CordaException
|
import net.corda.core.CordaException
|
||||||
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.internal.*
|
import net.corda.core.internal.*
|
||||||
import net.corda.core.utilities.contextLogger
|
import net.corda.core.utilities.contextLogger
|
||||||
import net.corda.core.utilities.minutes
|
import net.corda.core.utilities.minutes
|
||||||
import org.apache.commons.io.FileUtils
|
import net.corda.core.utilities.seconds
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
|
import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import java.nio.file.Paths
|
||||||
|
import java.nio.file.StandardCopyOption
|
||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.time.ZoneOffset.UTC
|
import java.time.ZoneOffset.UTC
|
||||||
@ -46,11 +50,16 @@ class Network private constructor(
|
|||||||
|
|
||||||
private var hasError = false
|
private var hasError = false
|
||||||
|
|
||||||
|
private var isDoormanNMSRunning = false
|
||||||
|
|
||||||
|
private lateinit var doormanNMS: JarCommand
|
||||||
|
|
||||||
init {
|
init {
|
||||||
targetDirectory.createDirectories()
|
targetDirectory.createDirectories()
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder internal constructor(
|
class Builder internal constructor(
|
||||||
|
private val networkType: Distribution.Type,
|
||||||
private val timeout: Duration
|
private val timeout: Duration
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@ -68,7 +77,9 @@ class Network private constructor(
|
|||||||
distribution: Distribution = Distribution.MASTER,
|
distribution: Distribution = Distribution.MASTER,
|
||||||
databaseType: DatabaseType = DatabaseType.H2,
|
databaseType: DatabaseType = DatabaseType.H2,
|
||||||
notaryType: NotaryType = NotaryType.NONE,
|
notaryType: NotaryType = NotaryType.NONE,
|
||||||
issuableCurrencies: List<String> = emptyList()
|
issuableCurrencies: List<String> = emptyList(),
|
||||||
|
compatibilityZoneURL: String? = null,
|
||||||
|
withRPCProxy: Boolean = false
|
||||||
): Builder {
|
): Builder {
|
||||||
return addNode(Node.new()
|
return addNode(Node.new()
|
||||||
.withName(name)
|
.withName(name)
|
||||||
@ -76,6 +87,8 @@ class Network private constructor(
|
|||||||
.withDatabaseType(databaseType)
|
.withDatabaseType(databaseType)
|
||||||
.withNotaryType(notaryType)
|
.withNotaryType(notaryType)
|
||||||
.withIssuableCurrencies(*issuableCurrencies.toTypedArray())
|
.withIssuableCurrencies(*issuableCurrencies.toTypedArray())
|
||||||
|
.withRPCProxy(withRPCProxy)
|
||||||
|
.withNetworkMap(compatibilityZoneURL)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,8 +108,11 @@ class Network private constructor(
|
|||||||
if (!network.configureNodes()) {
|
if (!network.configureNodes()) {
|
||||||
throw CordaException("Unable to configure nodes in Corda network. Please check logs in $directory")
|
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
|
return network
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,7 +120,7 @@ class Network private constructor(
|
|||||||
fun copyDatabaseDrivers() {
|
fun copyDatabaseDrivers() {
|
||||||
val driverDirectory = (targetDirectory / "libs").createDirectories()
|
val driverDirectory = (targetDirectory / "libs").createDirectories()
|
||||||
log.info("Copying database drivers from $stagingRoot/drivers to $driverDirectory")
|
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 {
|
fun configureNodes(): Boolean {
|
||||||
@ -125,8 +141,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() {
|
private fun bootstrapLocalNetwork() {
|
||||||
val bootstrapper = nodes.values
|
val bootstrapper = nodes.values
|
||||||
|
.filter { it.config.distribution.type != Distribution.Type.R3_CORDA }
|
||||||
.sortedByDescending { it.config.distribution.version }
|
.sortedByDescending { it.config.distribution.version }
|
||||||
.first()
|
.first()
|
||||||
.config.distribution.networkBootstrapper
|
.config.distribution.networkBootstrapper
|
||||||
@ -143,26 +303,25 @@ class Network private constructor(
|
|||||||
targetDirectory,
|
targetDirectory,
|
||||||
timeout
|
timeout
|
||||||
)
|
)
|
||||||
log.info("Running command: {}", command)
|
runCommand(command)
|
||||||
command.output.subscribe {
|
}
|
||||||
if (it.contains("Exception")) {
|
|
||||||
log.warn("Found error in output; interrupting bootstrapping action ...\n{}", it)
|
private fun bootstrapRPCProxy(node: Node) {
|
||||||
command.interrupt()
|
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()
|
else {
|
||||||
if (!command.waitFor()) {
|
log.warn("Missing RPC proxy startup script. Continuing ...")
|
||||||
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")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -205,6 +364,8 @@ class Network private constructor(
|
|||||||
for (node in nodes.values) {
|
for (node in nodes.values) {
|
||||||
log.info("Starting node [{}]", node.config.name)
|
log.info("Starting node [{}]", node.config.name)
|
||||||
node.start()
|
node.start()
|
||||||
|
if (node.rpcProxy)
|
||||||
|
bootstrapRPCProxy(node)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -269,8 +430,28 @@ class Network private constructor(
|
|||||||
log.info("Shutting down nodes ...")
|
log.info("Shutting down nodes ...")
|
||||||
for (node in nodes.values) {
|
for (node in nodes.values) {
|
||||||
node.shutDown()
|
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) {
|
fun use(action: (Network) -> Unit) {
|
||||||
@ -295,7 +476,7 @@ class Network private constructor(
|
|||||||
val log = contextLogger()
|
val log = contextLogger()
|
||||||
const val CLEANUP_ON_ERROR = false
|
const val CLEANUP_ON_ERROR = false
|
||||||
|
|
||||||
fun new(timeout: Duration = 2.minutes
|
fun new(type: Distribution.Type = Distribution.Type.CORDA, timeout: Duration = 2.minutes
|
||||||
): Builder = Builder(timeout)
|
): Builder = Builder(type, timeout)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,6 +11,9 @@
|
|||||||
package net.corda.behave.node
|
package net.corda.behave.node
|
||||||
|
|
||||||
import net.corda.behave.file.stagingRoot
|
import net.corda.behave.file.stagingRoot
|
||||||
|
import net.corda.behave.logging.getLogger
|
||||||
|
import net.corda.behave.service.Service
|
||||||
|
import net.corda.core.CordaRuntimeException
|
||||||
import net.corda.core.internal.copyTo
|
import net.corda.core.internal.copyTo
|
||||||
import net.corda.core.internal.createDirectories
|
import net.corda.core.internal.createDirectories
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
@ -24,6 +27,11 @@ import java.nio.file.Path
|
|||||||
*/
|
*/
|
||||||
class Distribution private constructor(
|
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.
|
* The version string of the Corda distribution.
|
||||||
*/
|
*/
|
||||||
@ -65,6 +73,16 @@ class Distribution private constructor(
|
|||||||
*/
|
*/
|
||||||
val networkBootstrapper: Path = path / "network-bootstrapper.jar"
|
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.
|
* Ensure that the distribution is available on disk.
|
||||||
*/
|
*/
|
||||||
@ -89,6 +107,11 @@ class Distribution private constructor(
|
|||||||
*/
|
*/
|
||||||
override fun toString() = "Corda(version = $version, path = $cordaJar)"
|
override fun toString() = "Corda(version = $version, path = $cordaJar)"
|
||||||
|
|
||||||
|
enum class Type {
|
||||||
|
CORDA,
|
||||||
|
R3_CORDA
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
||||||
private val log = contextLogger()
|
private val log = contextLogger()
|
||||||
@ -97,38 +120,46 @@ class Distribution private constructor(
|
|||||||
|
|
||||||
private val nodePrefix = stagingRoot / "corda"
|
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.
|
* 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.
|
* @param version The version of the Corda distribution.
|
||||||
*/
|
*/
|
||||||
fun fromArtifactory(version: String): Distribution {
|
fun fromArtifactory(type: Type, version: String): Distribution {
|
||||||
val url = URL("https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases/net/corda/corda/$version/corda-$version.jar")
|
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")
|
log.info("Artifactory URL: $url\n")
|
||||||
val distribution = Distribution(version, url = url)
|
val distribution = Distribution(type, version, url = url)
|
||||||
distributions.add(distribution)
|
distributions.add(distribution)
|
||||||
return distribution
|
return distribution
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get representation of a Corda distribution based on its version string and fat JAR path.
|
* 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 version The version of the Corda distribution.
|
||||||
* @param jarFile The path to the Corda fat JAR.
|
* @param jarFile The path to the Corda fat JAR.
|
||||||
*/
|
*/
|
||||||
fun fromJarFile(version: String, jarFile: Path? = null): Distribution {
|
fun fromJarFile(type: Type, version: String, jarFile: Path? = null): Distribution {
|
||||||
val distribution = Distribution(version, file = jarFile)
|
val distribution = Distribution(type, version, file = jarFile)
|
||||||
distributions.add(distribution)
|
distributions.add(distribution)
|
||||||
return distribution
|
return distribution
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Corda distribution from a Docker image file.
|
* 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 baseImage The name (eg. corda) of the Corda distribution.
|
||||||
* @param imageTag The version (github commit id or corda version) 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 {
|
fun fromDockerImage(type: Type, baseImage: String, imageTag: String): Distribution {
|
||||||
val distribution = Distribution(version = imageTag, baseImage = baseImage)
|
val distribution = Distribution(type, version = imageTag, baseImage = baseImage)
|
||||||
distributions.add(distribution)
|
distributions.add(distribution)
|
||||||
return distribution
|
return distribution
|
||||||
}
|
}
|
||||||
@ -139,8 +170,11 @@ class Distribution private constructor(
|
|||||||
*/
|
*/
|
||||||
fun fromVersionString(version: String): Distribution = when (version) {
|
fun fromVersionString(version: String): Distribution = when (version) {
|
||||||
"master" -> MASTER
|
"master" -> MASTER
|
||||||
"corda-3.0" -> fromArtifactory(version)
|
"r3-master" -> R3_MASTER
|
||||||
else -> fromJarFile(version)
|
"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.monitoring.PatternWatch
|
||||||
import net.corda.behave.node.configuration.*
|
import net.corda.behave.node.configuration.*
|
||||||
import net.corda.behave.process.JarCommand
|
import net.corda.behave.process.JarCommand
|
||||||
|
import net.corda.behave.process.JarCommandWithMain
|
||||||
import net.corda.behave.service.Service
|
import net.corda.behave.service.Service
|
||||||
import net.corda.behave.service.ServiceSettings
|
import net.corda.behave.service.ServiceSettings
|
||||||
|
import net.corda.behave.service.proxy.CordaRPCProxyClient
|
||||||
import net.corda.behave.ssh.MonitoringSSHClient
|
import net.corda.behave.ssh.MonitoringSSHClient
|
||||||
import net.corda.behave.ssh.SSHClient
|
import net.corda.behave.ssh.SSHClient
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||||
|
import net.corda.core.internal.createDirectories
|
||||||
import net.corda.core.internal.div
|
import net.corda.core.internal.div
|
||||||
import net.corda.core.internal.exists
|
import net.corda.core.internal.exists
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
import net.corda.core.utilities.NetworkHostAndPort
|
||||||
|
import net.corda.core.utilities.minutes
|
||||||
import net.corda.core.utilities.loggerFor
|
import net.corda.core.utilities.loggerFor
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import org.apache.commons.io.FileUtils
|
import org.apache.commons.io.FileUtils
|
||||||
@ -42,7 +46,9 @@ import java.util.concurrent.CountDownLatch
|
|||||||
class Node(
|
class Node(
|
||||||
val config: Configuration,
|
val config: Configuration,
|
||||||
private val rootDirectory: Path = currentDirectory,
|
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>()
|
private val log = loggerFor<Node>()
|
||||||
@ -89,16 +95,40 @@ class Node(
|
|||||||
log.info("Configuring {} ...", this)
|
log.info("Configuring {} ...", this)
|
||||||
serviceDependencies.addAll(config.database.type.dependencies(config))
|
serviceDependencies.addAll(config.database.type.dependencies(config))
|
||||||
config.distribution.ensureAvailable()
|
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()
|
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 {
|
fun start(): Boolean {
|
||||||
if (!startDependencies()) {
|
if (!startDependencies()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
log.info("Starting {} ...", this)
|
log.info("Starting {} ...", this)
|
||||||
return try {
|
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()
|
command.start()
|
||||||
isStarted = true
|
isStarted = true
|
||||||
true
|
true
|
||||||
@ -182,6 +212,20 @@ class Node(
|
|||||||
return result ?: error("Failed to run RPC action")
|
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 {
|
override fun toString(): String {
|
||||||
return "Node(name = ${config.name}, version = ${config.distribution.version})"
|
return "Node(name = ${config.name}, version = ${config.distribution.version})"
|
||||||
}
|
}
|
||||||
@ -248,6 +292,8 @@ class Node(
|
|||||||
|
|
||||||
private var notaryType = NotaryType.NONE
|
private var notaryType = NotaryType.NONE
|
||||||
|
|
||||||
|
private var compatibilityZoneURL: String? = null
|
||||||
|
|
||||||
private val issuableCurrencies = mutableListOf<String>()
|
private val issuableCurrencies = mutableListOf<String>()
|
||||||
|
|
||||||
private var location: String = "London"
|
private var location: String = "London"
|
||||||
@ -262,6 +308,10 @@ class Node(
|
|||||||
|
|
||||||
private var timeout = Duration.ofSeconds(60)
|
private var timeout = Duration.ofSeconds(60)
|
||||||
|
|
||||||
|
private var rpcProxy = false
|
||||||
|
|
||||||
|
var networkType = distribution.type
|
||||||
|
|
||||||
fun withName(newName: String): Builder {
|
fun withName(newName: String): Builder {
|
||||||
name = newName
|
name = newName
|
||||||
return this
|
return this
|
||||||
@ -269,6 +319,7 @@ class Node(
|
|||||||
|
|
||||||
fun withDistribution(newDistribution: Distribution): Builder {
|
fun withDistribution(newDistribution: Distribution): Builder {
|
||||||
distribution = newDistribution
|
distribution = newDistribution
|
||||||
|
networkType = distribution.type
|
||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,6 +333,16 @@ class Node(
|
|||||||
return this
|
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 {
|
fun withIssuableCurrencies(vararg currencies: String): Builder {
|
||||||
issuableCurrencies.addAll(currencies)
|
issuableCurrencies.addAll(currencies)
|
||||||
return this
|
return this
|
||||||
@ -318,9 +379,18 @@ class Node(
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun withRPCProxy(withRPCProxy: Boolean): Builder {
|
||||||
|
rpcProxy = withRPCProxy
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
fun build(): Node {
|
fun build(): Node {
|
||||||
val name = name ?: error("Node name not set")
|
val name = name ?: error("Node name not set")
|
||||||
val directory = directory ?: error("Runtime directory 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(
|
return Node(
|
||||||
Configuration(
|
Configuration(
|
||||||
name,
|
name,
|
||||||
@ -335,11 +405,14 @@ class Node(
|
|||||||
),
|
),
|
||||||
configElements = *arrayOf(
|
configElements = *arrayOf(
|
||||||
NotaryConfiguration(notaryType),
|
NotaryConfiguration(notaryType),
|
||||||
|
NetworkMapConfiguration(compatibilityZoneURL),
|
||||||
CurrencyConfiguration(issuableCurrencies)
|
CurrencyConfiguration(issuableCurrencies)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
directory,
|
directory,
|
||||||
ServiceSettings(timeout)
|
ServiceSettings(timeout),
|
||||||
|
rpcProxy = rpcProxy,
|
||||||
|
networkType = networkType
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ class Configuration(
|
|||||||
vararg configElements: ConfigurationTemplate
|
vararg configElements: ConfigurationTemplate
|
||||||
) {
|
) {
|
||||||
|
|
||||||
private val developerMode = true
|
private val developerMode = (distribution.type == Distribution.Type.CORDA)
|
||||||
|
|
||||||
val cordaX500Name: CordaX500Name by lazy({
|
val cordaX500Name: CordaX500Name by lazy({
|
||||||
CordaX500Name(name, location, country)
|
CordaX500Name(name, location, country)
|
||||||
|
@ -18,6 +18,7 @@ data class NetworkInterface(
|
|||||||
val sshPort: Int = getPort(2222 + nodeIndex),
|
val sshPort: Int = getPort(2222 + nodeIndex),
|
||||||
val p2pPort: Int = getPort(12001 + (nodeIndex * 5)),
|
val p2pPort: Int = getPort(12001 + (nodeIndex * 5)),
|
||||||
val rpcPort: Int = getPort(12002 + (nodeIndex * 5)),
|
val rpcPort: Int = getPort(12002 + (nodeIndex * 5)),
|
||||||
|
val rpcProxy: Int = getPort(13002 + (nodeIndex * 5)),
|
||||||
val rpcAdminPort: Int = getPort(12003 + (nodeIndex * 5)),
|
val rpcAdminPort: Int = getPort(12003 + (nodeIndex * 5)),
|
||||||
val webPort: Int = getPort(12004 + (nodeIndex * 5)),
|
val webPort: Int = getPort(12004 + (nodeIndex * 5)),
|
||||||
val dbPort: Int = getPort(12005 + (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
|
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
|
override val config: (Configuration) -> String
|
||||||
get() = {
|
get() = {
|
||||||
|
@ -14,7 +14,7 @@ import java.nio.file.Path
|
|||||||
import java.time.Duration
|
import java.time.Duration
|
||||||
|
|
||||||
class JarCommand(
|
class JarCommand(
|
||||||
jarFile: Path,
|
val jarFile: Path,
|
||||||
arguments: Array<out String>,
|
arguments: Array<out String>,
|
||||||
directory: Path,
|
directory: Path,
|
||||||
timeout: Duration,
|
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
|
||||||
|
}
|
@ -13,6 +13,7 @@ package net.corda.behave.scenarios
|
|||||||
import cucumber.api.java.After
|
import cucumber.api.java.After
|
||||||
import net.corda.behave.logging.getLogger
|
import net.corda.behave.logging.getLogger
|
||||||
import net.corda.behave.network.Network
|
import net.corda.behave.network.Network
|
||||||
|
import net.corda.behave.node.Distribution
|
||||||
import net.corda.behave.node.Node
|
import net.corda.behave.node.Node
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import org.assertj.core.api.Assertions.assertThat
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
@ -53,9 +54,15 @@ class ScenarioState {
|
|||||||
// Network is already running
|
// Network is already running
|
||||||
return
|
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) {
|
for (node in nodes) {
|
||||||
networkBuilder.addNode(node)
|
networkBuilder.addNode(node.withNetworkType(networkType))
|
||||||
}
|
}
|
||||||
network = networkBuilder.generate()
|
network = networkBuilder.generate()
|
||||||
network?.start()
|
network?.start()
|
||||||
@ -75,6 +82,14 @@ class ScenarioState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
inline fun <T> withClientProxy(nodeName: String, crossinline action: (CordaRPCOps) -> T): T {
|
||||||
|
withNetwork {
|
||||||
|
return node(nodeName).http {
|
||||||
|
action(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
fun stopNetwork() {
|
fun stopNetwork() {
|
||||||
val network = network ?: return
|
val network = network ?: return
|
||||||
|
@ -59,13 +59,13 @@ class Cash(state: ScenarioState) : Substeps(state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun transferCash(senderNode: String, sendToNode: String, amount: Long, currency: String): SignedTransaction {
|
fun transferCash(senderNode: String, sendToNode: String, amount: Long, currency: String): SignedTransaction {
|
||||||
return withClient(senderNode) {
|
return withClientProxy(senderNode) {
|
||||||
try {
|
try {
|
||||||
val sendToX500Name = node(sendToNode).config.cordaX500Name
|
val sendToX500Name = node(sendToNode).config.cordaX500Name
|
||||||
val sendToParty = node(senderNode).rpc {
|
val sendToParty = node(senderNode).rpc {
|
||||||
it.wellKnownPartyFromX500Name(sendToX500Name) ?: throw IllegalStateException("Unable to locate $sendToX500Name in Network Map Service")
|
it.wellKnownPartyFromX500Name(sendToX500Name) ?: throw IllegalStateException("Unable to locate $sendToX500Name in Network Map Service")
|
||||||
}
|
}
|
||||||
return@withClient it.startFlow(::CashPaymentFlow, Amount(amount, Currency.getInstance(currency)), sendToParty).returnValue.getOrThrow().stx
|
return@withClientProxy it.startFlow(::CashPaymentFlow, Amount(amount, Currency.getInstance(currency)), sendToParty).returnValue.getOrThrow().stx
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
log.warn("Failed to transfer $amount cash from $senderNode to $sendToNode", ex)
|
log.warn("Failed to transfer $amount cash from $senderNode to $sendToNode", ex)
|
||||||
throw ex
|
throw ex
|
||||||
|
@ -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'"))
|
?: 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 ->
|
Given<String, String, String>("^a (\\w+) notary (\\w+) of version ([^ ]+)$") { type, name, version ->
|
||||||
node(name)
|
node(name)
|
||||||
.withDistribution(Distribution.fromVersionString(version))
|
.withDistribution(Distribution.fromVersionString(version))
|
||||||
@ -70,7 +82,5 @@ class ConfigurationSteps : StepsBlock {
|
|||||||
Given<String, String>("^node (\\w+) has app installed: (.+)$") { name, app ->
|
Given<String, String>("^node (\\w+) has app installed: (.+)$") { name, app ->
|
||||||
node(name).withApp(app)
|
node(name).withApp(app)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ Feature: Cash - Issuable Currencies
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
| Node-Version |
|
| Node-Version |
|
||||||
| master |
|
| r3-master |
|
||||||
|
|
||||||
Scenario Outline: Node has an issuable currency
|
Scenario Outline: Node has an issuable currency
|
||||||
Given a node PartyA of version <Node-Version>
|
Given a node PartyA of version <Node-Version>
|
||||||
@ -21,7 +21,7 @@ Feature: Cash - Issuable Currencies
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
| Node-Version |
|
| Node-Version |
|
||||||
| master |
|
| r3-master |
|
||||||
|
|
||||||
Scenario Outline: Node can issue a currency
|
Scenario Outline: Node can issue a currency
|
||||||
Given a node PartyA of version <Node-Version>
|
Given a node PartyA of version <Node-Version>
|
||||||
@ -32,4 +32,4 @@ Feature: Cash - Issuable Currencies
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
| Node-Version |
|
| Node-Version |
|
||||||
| master |
|
| r3-master |
|
||||||
|
@ -10,7 +10,8 @@ Feature: Database - Connection
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
| Node-Version | Database-Type |
|
| Node-Version | Database-Type |
|
||||||
| master | H2 |
|
| r3-master | H2 |
|
||||||
|
|
||||||
# To run this scenario using postgreSQL you must ensure that Docker is running locally
|
# To run this scenario using other DB providers you must ensure that Docker is running locally
|
||||||
# | master | postgreSQL |
|
# | r3-master | postgreSQL |
|
||||||
|
# | r3-master | SQL Server |
|
@ -12,7 +12,7 @@ Feature: Startup Information - Logging
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
| Node-Version | Database-Type |
|
| Node-Version | Database-Type |
|
||||||
| master | H2 |
|
| r3-master | H2 |
|
||||||
|
|
||||||
Scenario Outline: Node shows database details on startup
|
Scenario Outline: Node shows database details on startup
|
||||||
Given a node PartyA of version <Node-Version>
|
Given a node PartyA of version <Node-Version>
|
||||||
@ -22,7 +22,7 @@ Feature: Startup Information - Logging
|
|||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
| Node-Version | Database-Type |
|
| Node-Version | Database-Type |
|
||||||
| master | H2 |
|
| r3-master | H2 |
|
||||||
|
|
||||||
Scenario Outline: Node shows version information on startup
|
Scenario Outline: Node shows version information on startup
|
||||||
Given a node PartyA of version <Node-Version>
|
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>
|
And node PartyA is on release version <Release-Version>
|
||||||
|
|
||||||
Examples:
|
Examples:
|
||||||
| Node-Version | Platform-Version | Release-Version |
|
| Node-Version | Platform-Version | Release-Version |
|
||||||
| master | 4 | corda-4.0-snapshot |
|
| 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
|
../../gradlew behaveJar
|
||||||
|
|
||||||
BEHAVE_JAR=$(ls build/libs/corda-behave-*.jar | tail -n1)
|
BEHAVE_JAR=$(ls build/libs/corda-behave-*.jar | tail -n1)
|
||||||
STAGING_ROOT=~/staging
|
STAGING_ROOT=~/staging
|
||||||
|
|
||||||
# startup
|
# startup
|
||||||
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/startup/logging.feature
|
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
|
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/cash/currencies.feature
|
||||||
|
|
||||||
# database
|
# 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,43 @@
|
|||||||
|
package net.corda.behave.process
|
||||||
|
|
||||||
|
import net.corda.behave.file.currentDirectory
|
||||||
|
import net.corda.behave.node.Distribution
|
||||||
|
import net.corda.core.internal.div
|
||||||
|
import net.corda.core.utilities.minutes
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
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
|
||||||
|
*/
|
||||||
|
|
||||||
|
// TODO: use environment variables to specify build location & SQL driver to use
|
||||||
|
// Configure the following to point to valid Corda node configurations
|
||||||
|
private val nodeRunDir = currentDirectory / "build" / "runs" / "PartyA"
|
||||||
|
private val jdbcDriver = nodeRunDir / ".." / "libs" / "postgresql-42.1.4.jar"
|
||||||
|
|
||||||
|
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,112 @@
|
|||||||
|
package net.corda.behave.process
|
||||||
|
|
||||||
|
import net.corda.behave.file.currentDirectory
|
||||||
|
import net.corda.behave.file.doormanConfigDirectory
|
||||||
|
import net.corda.behave.network.Network
|
||||||
|
import net.corda.behave.node.Distribution
|
||||||
|
import net.corda.behave.node.configuration.NotaryType
|
||||||
|
import net.corda.core.internal.div
|
||||||
|
import net.corda.core.utilities.minutes
|
||||||
|
import org.assertj.core.api.Assertions.assertThat
|
||||||
|
import org.junit.AfterClass
|
||||||
|
import org.junit.BeforeClass
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|
// TODO: use environment variables to specify build location & SQL driver to use
|
||||||
|
// Configure the following to point to valid Corda node configurations
|
||||||
|
private val notaryRunDir = currentDirectory / "build" / "runs" / "Notary"
|
||||||
|
private val participantRunDir = currentDirectory / "build" / "runs" / "PartyA"
|
||||||
|
|
||||||
|
@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(participantRunDir)
|
||||||
|
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", "$participantRunDir"),
|
||||||
|
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
|
package net.corda.behave.network
|
||||||
|
|
||||||
import net.corda.behave.database.DatabaseType
|
import net.corda.behave.database.DatabaseType
|
||||||
|
import net.corda.behave.node.Distribution
|
||||||
import net.corda.behave.node.configuration.NotaryType
|
import net.corda.behave.node.configuration.NotaryType
|
||||||
|
import net.corda.core.utilities.hours
|
||||||
import net.corda.core.utilities.seconds
|
import net.corda.core.utilities.seconds
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
import java.time.Duration
|
||||||
|
|
||||||
class NetworkTests {
|
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
|
@Ignore
|
||||||
@Test
|
@Test
|
||||||
fun `network of two nodes can be spun up`() {
|
fun `network of two nodes can be spun up`() {
|
||||||
|
@ -31,7 +31,7 @@ class CommandTests {
|
|||||||
@Test
|
@Test
|
||||||
fun `output stream for command can be observed`() {
|
fun `output stream for command can be observed`() {
|
||||||
val subscriber = TestSubscriber<String>()
|
val subscriber = TestSubscriber<String>()
|
||||||
val exitCode = Command(listOf("ls", "/")).use(subscriber) { _, output ->
|
val exitCode = Command(listOf("ls", "/")).use(subscriber) { _, _ ->
|
||||||
subscriber.awaitTerminalEvent()
|
subscriber.awaitTerminalEvent()
|
||||||
subscriber.assertCompleted()
|
subscriber.assertCompleted()
|
||||||
subscriber.assertNoErrors()
|
subscriber.assertNoErrors()
|
||||||
|
@ -10,6 +10,8 @@
|
|||||||
|
|
||||||
buildscript {
|
buildscript {
|
||||||
ext.strata_version = '1.1.2'
|
ext.strata_version = '1.1.2'
|
||||||
|
ext.corda_release_group = 'net.corda'
|
||||||
|
ext.cucumber_version = '1.2.5'
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
@ -32,11 +34,24 @@ sourceSets {
|
|||||||
srcDir file('../testing/test-utils/src/main/resources')
|
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 {
|
configurations {
|
||||||
integrationTestCompile.extendsFrom testCompile
|
integrationTestCompile.extendsFrom testCompile
|
||||||
integrationTestRuntime.extendsFrom testRuntime
|
integrationTestRuntime.extendsFrom testRuntime
|
||||||
|
|
||||||
|
scenarioTestCompile.extendsFrom testCompile
|
||||||
|
scenarioTestRuntime.extendsFrom testRuntime
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
@ -69,6 +84,7 @@ dependencies {
|
|||||||
|
|
||||||
// Test dependencies
|
// Test dependencies
|
||||||
testCompile project(':node-driver')
|
testCompile project(':node-driver')
|
||||||
|
scenarioTestCompile project(path: ":experimental:behave", configuration: 'testArtifacts')
|
||||||
testCompile "junit:junit:$junit_version"
|
testCompile "junit:junit:$junit_version"
|
||||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||||
}
|
}
|
||||||
@ -147,6 +163,16 @@ task integrationTest(type: Test, dependsOn: []) {
|
|||||||
classpath = sourceSets.integrationTest.runtimeClasspath
|
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 {
|
publishing {
|
||||||
publications {
|
publications {
|
||||||
simmvaluationdemo(MavenPublication) {
|
simmvaluationdemo(MavenPublication) {
|
||||||
@ -155,6 +181,7 @@ publishing {
|
|||||||
|
|
||||||
artifact sourceJar
|
artifact sourceJar
|
||||||
artifact javadocJar
|
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
|
@ -1,85 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.
|
|
||||||
*/
|
|
||||||
|
|
||||||
group 'net.corda.behave.tools'
|
|
||||||
|
|
||||||
apply plugin: 'java'
|
|
||||||
apply plugin: 'kotlin'
|
|
||||||
apply plugin: 'maven-publish'
|
|
||||||
|
|
||||||
configurations {
|
|
||||||
smokeTestCompile.extendsFrom testCompile
|
|
||||||
smokeTestRuntime.extendsFrom testRuntime
|
|
||||||
}
|
|
||||||
|
|
||||||
sourceSets {
|
|
||||||
rpcProxy {
|
|
||||||
kotlin {
|
|
||||||
srcDir "src/main/kotlin"
|
|
||||||
}
|
|
||||||
resources {
|
|
||||||
srcDir "config/dev"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
smokeTest {
|
|
||||||
kotlin {
|
|
||||||
compileClasspath += main.output + test.output
|
|
||||||
runtimeClasspath += main.output + test.output
|
|
||||||
srcDir file('src/smoke-test/kotlin')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compile project(':test-utils')
|
|
||||||
compile "net.corda.plugins:cordform-common:$gradle_plugins_version"
|
|
||||||
|
|
||||||
// Integration test helpers
|
|
||||||
testCompile "org.assertj:assertj-core:$assertj_version"
|
|
||||||
testCompile "junit:junit:$junit_version"
|
|
||||||
|
|
||||||
compile project(':finance')
|
|
||||||
compileOnly project(':node-api')
|
|
||||||
compile project(':core')
|
|
||||||
compile project(':client:rpc')
|
|
||||||
|
|
||||||
// includes jetty/jersey dependencies used by RPCProxyServer
|
|
||||||
compile project(':webserver')
|
|
||||||
|
|
||||||
// Jetty http server
|
|
||||||
rpcProxyCompile "org.eclipse.jetty:jetty-servlet:$jetty_version"
|
|
||||||
rpcProxyCompile "org.eclipse.jetty:jetty-webapp:$jetty_version"
|
|
||||||
rpcProxyCompile "javax.servlet:javax.servlet-api:3.1.0"
|
|
||||||
|
|
||||||
// Jersey for JAX-RS implementation for use in Jetty
|
|
||||||
rpcProxyCompile "org.glassfish.jersey.core:jersey-server:$jersey_version"
|
|
||||||
rpcProxyCompile "org.glassfish.jersey.containers:jersey-container-servlet-core:$jersey_version"
|
|
||||||
rpcProxyCompile "org.glassfish.jersey.containers:jersey-container-jetty-http:$jersey_version"
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
task smokeTest(type: Test) {
|
|
||||||
testClassesDirs = sourceSets.smokeTest.output.classesDirs
|
|
||||||
classpath = sourceSets.smokeTest.runtimeClasspath
|
|
||||||
}
|
|
||||||
|
|
||||||
task rpcProxyJar(type: Jar) {
|
|
||||||
baseName "corda-rpcProxy"
|
|
||||||
from {
|
|
||||||
configurations.rpcProxyRuntime.collect { it.isDirectory() ? it : zipTree(it) }
|
|
||||||
}
|
|
||||||
with jar
|
|
||||||
exclude("META-INF/*.DSA")
|
|
||||||
exclude("META-INF/*.RSA")
|
|
||||||
exclude("META-INF/*.SF")
|
|
||||||
manifest {
|
|
||||||
attributes 'Main-Class': 'net.corda.behave.service.proxy.RPCProxyServerKt'
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,163 +0,0 @@
|
|||||||
package net.corda.behave.service.proxy
|
|
||||||
|
|
||||||
import net.corda.behave.service.proxy.RPCProxyWebService.Companion.RPC_PROXY_PATH
|
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
|
||||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
|
||||||
import net.corda.client.rpc.internal.KryoClientSerializationScheme
|
|
||||||
import net.corda.core.contracts.ContractState
|
|
||||||
import net.corda.core.flows.FlowLogic
|
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
|
||||||
import net.corda.core.serialization.deserialize
|
|
||||||
import net.corda.core.serialization.internal.effectiveSerializationEnv
|
|
||||||
import net.corda.core.serialization.serialize
|
|
||||||
import net.corda.core.utilities.NetworkHostAndPort
|
|
||||||
import net.corda.core.utilities.contextLogger
|
|
||||||
import net.corda.core.utilities.getOrThrow
|
|
||||||
import net.corda.core.utilities.seconds
|
|
||||||
import java.io.InputStream
|
|
||||||
import javax.servlet.http.HttpServletRequest
|
|
||||||
import javax.ws.rs.Consumes
|
|
||||||
import javax.ws.rs.GET
|
|
||||||
import javax.ws.rs.POST
|
|
||||||
import javax.ws.rs.Path
|
|
||||||
import javax.ws.rs.core.Context
|
|
||||||
import javax.ws.rs.core.MediaType
|
|
||||||
import javax.ws.rs.core.Response
|
|
||||||
import javax.ws.rs.core.Response.status
|
|
||||||
|
|
||||||
@Path(RPC_PROXY_PATH)
|
|
||||||
class RPCProxyWebService(targetHostAndPort: NetworkHostAndPort) {
|
|
||||||
|
|
||||||
// see "NetworkInterface" port allocation definitions
|
|
||||||
private val targetPort = targetHostAndPort.port - 1000
|
|
||||||
|
|
||||||
init {
|
|
||||||
try {
|
|
||||||
effectiveSerializationEnv
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
try {
|
|
||||||
KryoClientSerializationScheme.initialiseSerialization()
|
|
||||||
} catch (e: IllegalStateException) {
|
|
||||||
// Race e.g. two of these constructed in parallel, ignore.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private val log = contextLogger()
|
|
||||||
const val DEFAULT_PASSWORD = "S0meS3cretW0rd"
|
|
||||||
const val RPC_PROXY_PATH = "rpc"
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("my-ip")
|
|
||||||
fun myIp(@Context request: HttpServletRequest): Response {
|
|
||||||
return createResponse("HELLO! My ip is ${request.remoteHost}:${request.remotePort}")
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("node-info")
|
|
||||||
fun nodeInfo(@Context request: HttpServletRequest): Response {
|
|
||||||
log.info("nodeInfo")
|
|
||||||
return use {
|
|
||||||
it.nodeInfo()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("registered-flows")
|
|
||||||
fun registeredFlows(@Context request: HttpServletRequest): Response {
|
|
||||||
log.info("registeredFlows")
|
|
||||||
return use {
|
|
||||||
it.registeredFlows()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("notary-identities")
|
|
||||||
fun notaryIdentities(@Context request: HttpServletRequest): Response {
|
|
||||||
log.info("networkMapSnapshot")
|
|
||||||
return use {
|
|
||||||
it.notaryIdentities()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@GET
|
|
||||||
@Path("network-map-snapshot")
|
|
||||||
fun networkMapSnapshot(@Context request: HttpServletRequest): Response {
|
|
||||||
log.info("networkMapSnapshot")
|
|
||||||
return use {
|
|
||||||
it.networkMapSnapshot()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("parties-from-name")
|
|
||||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
|
||||||
fun partiesFromName(input: InputStream): Response {
|
|
||||||
log.info("partiesFromName")
|
|
||||||
val queryName = input.readBytes().deserialize<String>()
|
|
||||||
return use {
|
|
||||||
it.partiesFromName(queryName, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("vault-query")
|
|
||||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
|
||||||
fun vaultQuery(input: InputStream): Response {
|
|
||||||
log.info("vaultQuery")
|
|
||||||
val contractStateType = input.readBytes().deserialize<String>()
|
|
||||||
val clazz = Class.forName(contractStateType) as Class<ContractState>
|
|
||||||
return use {
|
|
||||||
log.info("Calling vaultQuery with: $clazz")
|
|
||||||
it.vaultQuery(clazz)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@POST
|
|
||||||
@Path("start-flow")
|
|
||||||
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
|
|
||||||
fun startFlow(input: InputStream): Response {
|
|
||||||
log.info("startFlow")
|
|
||||||
return use { rpcClient ->
|
|
||||||
val argsList = input.readBytes().deserialize<List<Any>>()
|
|
||||||
for (i in argsList.indices) {
|
|
||||||
log.info("$i: ${argsList[i]}")
|
|
||||||
}
|
|
||||||
val flowClass = Class.forName(argsList[0] as String) as Class<FlowLogic<*>>
|
|
||||||
val flowArgs = argsList.drop(1).toTypedArray()
|
|
||||||
log.info("Calling flow: $flowClass with arguments: ${flowArgs.asList()}")
|
|
||||||
rpcClient.startFlowDynamic(flowClass, *flowArgs).returnValue.getOrThrow()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun <T> use(action: (CordaRPCOps) -> T): Response {
|
|
||||||
val targetHost = NetworkHostAndPort("localhost", targetPort)
|
|
||||||
val config = object : CordaRPCClientConfiguration {
|
|
||||||
override val connectionMaxRetryInterval = 10.seconds
|
|
||||||
}
|
|
||||||
log.info("Establishing RPC connection to ${targetHost.host} on port ${targetHost.port} ...")
|
|
||||||
return try {
|
|
||||||
CordaRPCClient(targetHost, config).use("corda", DEFAULT_PASSWORD) {
|
|
||||||
log.info("RPC connection to ${targetHost.host}:${targetHost.port} established")
|
|
||||||
val client = it.proxy
|
|
||||||
val result = action(client)
|
|
||||||
log.info("CordaRPCOps result: $result")
|
|
||||||
return createResponse(result)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
log.warn("RPC Proxy request failed: ", e)
|
|
||||||
e.printStackTrace()
|
|
||||||
status(Response.Status.INTERNAL_SERVER_ERROR).encoding(e.message)
|
|
||||||
}.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createResponse(payload: Any?): Response {
|
|
||||||
return if (payload != null) {
|
|
||||||
Response.ok(payload.serialize().bytes)
|
|
||||||
} else {
|
|
||||||
status(Response.Status.NOT_FOUND)
|
|
||||||
}.build()
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user