Merge branch 'master' into kat-merge-20180508

This commit is contained in:
Michele Sollecito 2018-05-08 21:52:24 +07:00 committed by GitHub
commit 1963c29f01
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
46 changed files with 1297 additions and 79 deletions

6
.idea/compiler.xml generated
View File

@ -12,9 +12,11 @@
<module name="bank-of-corda-demo_test" target="1.8" />
<module name="behave-tools_main" target="1.8" />
<module name="behave-tools_test" target="1.8" />
<module name="behave_api" target="1.8" />
<module name="behave_behave" target="1.8" />
<module name="behave_main" target="1.8" />
<module name="behave_scenario" target="1.8" />
<module name="behave_smokeTest" target="1.8" />
<module name="behave_test" target="1.8" />
<module name="blobinspector_main" target="1.8" />
<module name="blobinspector_test" target="1.8" />
@ -73,6 +75,8 @@
<module name="example-code_test" target="1.8" />
<module name="experimental-behave_behave" target="1.8" />
<module name="experimental-behave_main" target="1.8" />
<module name="experimental-behave_scenario" target="1.8" />
<module name="experimental-behave_smokeTest" target="1.8" />
<module name="experimental-behave_test" target="1.8" />
<module name="experimental-kryo-hook_main" target="1.8" />
<module name="experimental-kryo-hook_test" target="1.8" />
@ -169,6 +173,8 @@
<module name="shell_test" target="1.8" />
<module name="simm-valuation-demo_integrationTest" target="1.8" />
<module name="simm-valuation-demo_main" target="1.8" />
<module name="simm-valuation-demo_scenario" target="1.8" />
<module name="simm-valuation-demo_scenarioTest" target="1.8" />
<module name="simm-valuation-demo_test" target="1.8" />
<module name="smoke-test-utils_main" target="1.8" />
<module name="smoke-test-utils_test" target="1.8" />

View File

@ -31,6 +31,7 @@ group 'net.corda.behave'
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'net.corda.plugins.publish-utils'
sourceCompatibility = 1.8
@ -43,15 +44,30 @@ sourceSets {
java {
compileClasspath += main.output
runtimeClasspath += main.output
srcDirs = ["src/main/kotlin", "src/scenario/kotlin"]
srcDirs = ['src/main/kotlin', 'src/scenario/kotlin']
}
resources.srcDir file('src/scenario/resources')
}
smokeTest {
kotlin {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/smoke-test/kotlin')
}
resources {
srcDirs = ['src/scenario/resources']
}
}
}
configurations {
behaveCompile.extendsFrom testCompile
behaveRuntime.extendsFrom testRuntime
smokeTestCompile.extendsFrom testCompile
smokeTestRuntime.extendsFrom testRuntime
testArtifacts.extendsFrom behaveRuntime
}
dependencies {
@ -91,13 +107,16 @@ dependencies {
compile project(':node-api')
compile project(':client:rpc')
// Unit Tests
// dependency on CordaHttpToRPC proxy
compile project(':testing:qa:behave:tools:rpc-proxy')
testCompile project(':test-utils')
// Unit Tests
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:$assertj_version"
// Scenarios / End-to-End Tests
behaveCompile "info.cukes:cucumber-java8:$cucumber_version"
behaveCompile "info.cukes:cucumber-junit:$cucumber_version"
behaveCompile "info.cukes:cucumber-picocontainer:$cucumber_version"
@ -115,6 +134,11 @@ test {
testLogging.showStandardStreams = true
}
task smokeTest(type: Test) {
testClassesDirs = sourceSets.smokeTest.output.classesDirs
classpath = sourceSets.smokeTest.runtimeClasspath
}
task behaveJar(type: Jar) {
baseName "corda-behave"
from sourceSets.behave.output
@ -133,3 +157,24 @@ task behaveJar(type: Jar) {
attributes 'Main-Class': 'net.corda.behave.scenarios.ScenarioRunner'
}
}
task apiJar(type: Jar, dependsOn: classes) {
baseName "corda-behave-api"
from sourceSets.behave.output
from {
configurations.behaveCompile.collect {
it.isDirectory() ? it : zipTree(it)
}
}
from project(':client:rpc').configurations.compile.collect { zipTree it }
with jar
include 'net/corda/behave/scenarios/**'
include 'cucumber/api/**'
include 'io/github/lukehutch/**'
exclude '**/features/**'
exclude '**/scripts/**'
}
artifacts {
testArtifacts apiJar
}

View File

@ -6,7 +6,7 @@ set -x
# For example:
# corda-master => git clone https://github.com/corda/corda
# r3corda-master => git clone https://github.com/corda/enterprise
VERSION=corda-master
VERSION=r3corda-master
STAGING_DIR=~/staging
CORDA_DIR=${STAGING_DIR}/corda/${VERSION}
CORDAPP_DIR=${CORDA_DIR}/apps
@ -18,18 +18,32 @@ mkdir -p ${CORDA_DIR}
mkdir -p ${CORDAPP_DIR}
mkdir -p ${DRIVERS_DIR}
# Copy Corda capsule into deps
# Copy Corda capsule into staging
cd ../..
./gradlew clean :node:capsule:buildCordaJar :finance:jar
./gradlew :node:capsule:buildCordaJar :finance:jar
cp -v $(ls node/capsule/build/libs/corda-*.jar | tail -n1) ${CORDA_DIR}/corda.jar
# Copy finance library
cp -v $(ls finance/build/libs/corda-finance-*.jar | tail -n1) ${CORDAPP_DIR}
# Copy sample Cordapps
./gradlew samples:simm-valuation-demo:jar
cp -v $(ls samples/simm-valuation-demo/build/libs/simm-valuation-demo-*.jar | tail -n1) ${CORDAPP_DIR}
# Download database drivers
curl "https://search.maven.org/remotecontent?filepath=com/h2database/h2/1.4.196/h2-1.4.196.jar" > ${DRIVERS_DIR}/h2-1.4.196.jar
curl -L "http://central.maven.org/maven2/org/postgresql/postgresql/42.1.4/postgresql-42.1.4.jar" > ${DRIVERS_DIR}/postgresql-42.1.4.jar
curl -L "https://github.com/Microsoft/mssql-jdbc/releases/download/v6.2.2/mssql-jdbc-6.2.2.jre8.jar" > ${DRIVERS_DIR}/mssql-jdbc-6.2.2.jre8.jar
# Build Network Bootstrapper
./gradlew buildBootstrapperJar
cp -v $(ls tools/bootstrapper/build/libs/*.jar | tail -n1) ${CORDA_DIR}/network-bootstrapper.jar
# build and distribute Doorman/NMS
./gradlew network-management:capsule:buildDoormanJAR
cp -v $(ls network-management/capsule/build/libs/doorman-*.jar | tail -n1) ${CORDA_DIR}/doorman.jar
# build and distribute DB Migration tool
./gradlew tools:dbmigration:shadowJar
cp -v $(ls tools/dbmigration/build/libs/*migration-*.jar | tail -n1) ${CORDA_DIR}/dbmigration.jar

View File

@ -12,6 +12,7 @@ package net.corda.behave.database
import net.corda.behave.database.configuration.H2ConfigurationTemplate
import net.corda.behave.database.configuration.PostgresConfigurationTemplate
import net.corda.behave.database.configuration.SqlServerConfigurationTemplate
import net.corda.behave.node.configuration.Configuration
import net.corda.behave.node.configuration.DatabaseConfiguration
import net.corda.behave.service.database.H2Service
@ -29,8 +30,18 @@ enum class DatabaseType(val settings: DatabaseSettings) {
}
),
SQL_SERVER(DatabaseSettings()
.withDatabase(SqlServerService.database)
.withDriver(SqlServerService.driver)
.withSchema(SqlServerService.schema)
.withUser(SqlServerService.username)
.withConfigTemplate(SqlServerConfigurationTemplate())
.withServiceInitiator {
PostgreSQLService("postgres-${it.name}", it.database.port, it.database.password)
}
),
POSTGRES(DatabaseSettings()
.withDatabase(PostgreSQLService.database)
.withDriver(PostgreSQLService.driver)
.withSchema(PostgreSQLService.schema)
.withUser(PostgreSQLService.username)
@ -48,8 +59,9 @@ enum class DatabaseType(val settings: DatabaseSettings) {
companion object {
fun fromName(name: String): DatabaseType? = when (name.toLowerCase()) {
fun fromName(name: String): DatabaseType? = when (name.replace("[ _-]".toRegex(), "").toLowerCase()) {
"h2" -> H2
"sqlserver" -> SQL_SERVER
"postgres" -> POSTGRES
"postgresql" -> POSTGRES
else -> null

View File

@ -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}"
|}
"""
}
}

View File

@ -10,6 +10,7 @@
package net.corda.behave.file
import net.corda.core.internal.div
import java.nio.file.Path
import java.nio.file.Paths
@ -19,3 +20,9 @@ val currentDirectory: Path
// location of Corda distributions and Drivers dependencies
val stagingRoot: Path
get() = System.getProperty("STAGING_ROOT")?.let { Paths.get(it) } ?: currentDirectory
val doormanConfigDirectory: Path
get() = currentDirectory / "src" / "main" / "resources" / "doorman"
val tmpDirectory: Path
get() = System.getProperty("TMPDIR")?.let { Paths.get(it) } ?: Paths.get("/tmp")

View File

@ -11,20 +11,23 @@
package net.corda.behave.network
import net.corda.behave.database.DatabaseType
import net.corda.behave.file.LogSource
import net.corda.behave.file.currentDirectory
import net.corda.behave.file.stagingRoot
import net.corda.behave.file.*
import net.corda.behave.monitoring.PatternWatch
import net.corda.behave.node.Distribution
import net.corda.behave.node.Node
import net.corda.behave.node.configuration.NotaryType
import net.corda.behave.process.Command
import net.corda.behave.process.JarCommand
import net.corda.core.CordaException
import net.corda.core.CordaRuntimeException
import net.corda.core.internal.*
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.minutes
import org.apache.commons.io.FileUtils
import net.corda.core.utilities.seconds
import java.io.Closeable
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.StandardCopyOption
import java.time.Duration
import java.time.Instant
import java.time.ZoneOffset.UTC
@ -46,11 +49,16 @@ class Network private constructor(
private var hasError = false
private var isDoormanNMSRunning = false
private lateinit var doormanNMS: JarCommand
init {
targetDirectory.createDirectories()
}
class Builder internal constructor(
private val networkType: Distribution.Type,
private val timeout: Duration
) {
@ -68,7 +76,9 @@ class Network private constructor(
distribution: Distribution = Distribution.MASTER,
databaseType: DatabaseType = DatabaseType.H2,
notaryType: NotaryType = NotaryType.NONE,
issuableCurrencies: List<String> = emptyList()
issuableCurrencies: List<String> = emptyList(),
compatibilityZoneURL: String? = null,
withRPCProxy: Boolean = false
): Builder {
return addNode(Node.new()
.withName(name)
@ -76,6 +86,8 @@ class Network private constructor(
.withDatabaseType(databaseType)
.withNotaryType(notaryType)
.withIssuableCurrencies(*issuableCurrencies.toTypedArray())
.withRPCProxy(withRPCProxy)
.withNetworkMap(compatibilityZoneURL)
)
}
@ -95,8 +107,11 @@ class Network private constructor(
if (!network.configureNodes()) {
throw CordaException("Unable to configure nodes in Corda network. Please check logs in $directory")
}
network.bootstrapLocalNetwork()
if (networkType == Distribution.Type.R3_CORDA)
network.bootstrapDoorman()
else
network.bootstrapLocalNetwork()
return network
}
}
@ -104,7 +119,7 @@ class Network private constructor(
fun copyDatabaseDrivers() {
val driverDirectory = (targetDirectory / "libs").createDirectories()
log.info("Copying database drivers from $stagingRoot/drivers to $driverDirectory")
FileUtils.copyDirectory((stagingRoot / "drivers").toFile(), driverDirectory.toFile())
Files.copy((stagingRoot / "drivers"), driverDirectory, StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING)
}
fun configureNodes(): Boolean {
@ -125,8 +140,152 @@ class Network private constructor(
}
}
/**
* This method performs the configuration steps defined in the R3 Corda Network Management readme:
* https://github.com/corda/enterprise/blob/master/network-management/README.md
* using Local signing and "Auto Approval" mode
*/
private fun bootstrapDoorman() {
// WARNING!! Need to use the correct bootstrapper
// only if using OS nodes (need to choose the latest version)
val r3node = nodes.values
.find { it.config.distribution.type == Distribution.Type.R3_CORDA } ?: throw CordaRuntimeException("Missing R3 distribution node")
val distribution = r3node.config.distribution
// Copy over reference configuration files used in bootstrapping
val source = doormanConfigDirectory
val doormanTargetDirectory = targetDirectory / "doorman"
source.toFile().copyRecursively(doormanTargetDirectory.toFile(), true)
// 1. Create key stores for local signer
// java -jar doorman-<version>.jar --mode ROOT_KEYGEN
log.info("Doorman target directory: $doormanTargetDirectory")
runCommand(JarCommand(distribution.doormanJar,
arrayOf("--config-file", "$doormanConfigDirectory/node-init.conf", "--mode", "ROOT_KEYGEN", "--trust-store-password", "password"),
doormanTargetDirectory, timeout))
// java -jar doorman-<version>.jar --mode CA_KEYGEN
runCommand(JarCommand(distribution.doormanJar,
arrayOf("--config-file", "$doormanConfigDirectory/node-init.conf", "--mode", "CA_KEYGEN"),
doormanTargetDirectory, timeout))
// 2. Start the doorman service for notary registration
doormanNMS = JarCommand(distribution.doormanJar,
arrayOf("--config-file", "$doormanConfigDirectory/node-init.conf"),
doormanTargetDirectory, timeout)
val doormanCommand = runCommand(doormanNMS, noWait = true)
log.info("Waiting for DoormanNMS to be alive")
PatternWatch(doormanCommand.output, "Network management web services started on").await(30.seconds)
log.info("DoormanNMS up and running")
// Notary Nodes
val notaryNodes = nodes.values.filter { it.config.notary.notaryType != NotaryType.NONE }
notaryNodes.forEach { notaryNode ->
val notaryTargetDirectory = targetDirectory / notaryNode.config.name
log.info("Notary target directory: $notaryTargetDirectory")
// 3. Create notary node and register with the doorman
runCommand(JarCommand(distribution.cordaJar,
arrayOf("--initial-registration",
"--base-directory", "$notaryTargetDirectory",
"--network-root-truststore", "../doorman/certificates/distribute-nodes/network-root-truststore.jks",
"--network-root-truststore-password", "password"),
notaryTargetDirectory, timeout))
// 4. Generate node info files for notary nodes
runCommand(JarCommand(distribution.cordaJar,
arrayOf("--just-generate-node-info",
"--base-directory", "$notaryTargetDirectory"),
notaryTargetDirectory, timeout))
// cp (or ln -s) nodeInfo* notary-node-info
val nodeInfoFile = notaryTargetDirectory.toFile().listFiles { _, filename -> filename.matches("nodeInfo-.+".toRegex()) }.firstOrNull() ?: throw CordaRuntimeException("Missing notary nodeInfo file")
Files.copy(nodeInfoFile.toPath(), (notaryTargetDirectory / "notary-node-info"), StandardCopyOption.COPY_ATTRIBUTES, StandardCopyOption.REPLACE_EXISTING)
}
// exit Doorman process
doormanCommand.interrupt()
doormanCommand.waitFor()
// 5. Add notary identities to the network parameters
// 6. Load initial network parameters file for network map service
val networkParamsConfig = if (notaryNodes.isEmpty()) "network-parameters-without-notary.conf" else "network-parameters.conf"
val updateNetworkParams = JarCommand(distribution.doormanJar,
arrayOf("--config-file", "$doormanTargetDirectory/node.conf", "--set-network-parameters", "$doormanTargetDirectory/$networkParamsConfig"),
doormanTargetDirectory, timeout)
runCommand(updateNetworkParams)
// 7. Start a fully configured Doorman / NMS
doormanNMS = JarCommand(distribution.doormanJar,
arrayOf("--config-file", "$doormanConfigDirectory/node.conf"),
doormanTargetDirectory, timeout)
val doormanNMSCommand = runCommand(doormanNMS, noWait = true)
log.info("Waiting for DoormanNMS to be alive")
PatternWatch(doormanNMSCommand.output, "Network management web services started on").await(30.seconds)
log.info("DoormanNMS up and running")
// 8. Register other participant nodes
val partyNodes = nodes.values.filter { it.config.notary.notaryType == NotaryType.NONE }
partyNodes.forEach { partyNode ->
val partyTargetDirectory = targetDirectory / partyNode.config.name
log.info("Party target directory: $partyTargetDirectory")
// 3. Create notary node and register with the doorman
runCommand(JarCommand(distribution.cordaJar,
arrayOf("--initial-registration",
"--network-root-truststore", "../doorman/certificates/distribute-nodes/network-root-truststore.jks",
"--network-root-truststore-password", "password",
"--base-directory", "$partyTargetDirectory"),
partyTargetDirectory, timeout))
}
isDoormanNMSRunning = true
}
private fun runCommand(command: Command, noWait: Boolean = false): Command {
if (command is JarCommand) {
log.info("Checking existence of jar file:", command.jarFile)
if (!command.jarFile.exists()) {
throw IllegalStateException("Jar file does not exist: ${command.jarFile}")
}
}
log.info("Running command: {}", command)
command.output.subscribe {
if (it.contains("Exception")) {
log.warn("Found error in output; interrupting command execution ...\n{}", it)
command.interrupt()
}
}
command.start()
if (!noWait) {
if (!command.waitFor()) {
hasError = true
error("Failed to execute command") {
val matches = LogSource(targetDirectory)
.find(".*[Ee]xception.*")
.groupBy { it.filename.toAbsolutePath() }
for (match in matches) {
log.info("Log(${match.key}):\n${match.value.joinToString("\n") { it.contents }}")
}
}
} else {
log.info("Command executed successfully")
}
}
return command
}
private fun bootstrapLocalNetwork() {
val bootstrapper = nodes.values
.filter { it.config.distribution.type != Distribution.Type.R3_CORDA }
.sortedByDescending { it.config.distribution.version }
.first()
.config.distribution.networkBootstrapper
@ -143,26 +302,25 @@ class Network private constructor(
targetDirectory,
timeout
)
log.info("Running command: {}", command)
command.output.subscribe {
if (it.contains("Exception")) {
log.warn("Found error in output; interrupting bootstrapping action ...\n{}", it)
command.interrupt()
}
runCommand(command)
}
private fun bootstrapRPCProxy(node: Node) {
val cordaDistribution = node.config.distribution.path
val rpcProxyPortNo = node.config.nodeInterface.rpcProxy
val startProxyScript = cordaDistribution / "startRPCproxy.sh"
if (startProxyScript.exists()) {
log.info("Bootstrapping RPC proxy, please wait ...")
val rpcProxyLogFile = targetDirectory / node.config.name / "logs" / "startRPCproxy.log"
val rpcProxyCommand = Command(listOf("$startProxyScript", "$cordaDistribution", "$rpcProxyPortNo", ">>$rpcProxyLogFile", "2>&1"),
cordaDistribution,
timeout
)
runCommand(rpcProxyCommand)
}
command.start()
if (!command.waitFor()) {
hasError = true
error("Failed to bootstrap network") {
val matches = LogSource(targetDirectory)
.find(".*[Ee]xception.*")
.groupBy { it.filename.toAbsolutePath() }
for (match in matches) {
log.info("Log(${match.key}):\n${match.value.joinToString("\n") { it.contents }}")
}
}
} else {
log.info("Network set-up completed")
else {
log.warn("Missing RPC proxy startup script. Continuing ...")
}
}
@ -205,6 +363,8 @@ class Network private constructor(
for (node in nodes.values) {
log.info("Starting node [{}]", node.config.name)
node.start()
if (node.rpcProxy)
bootstrapRPCProxy(node)
}
}
@ -269,8 +429,28 @@ class Network private constructor(
log.info("Shutting down nodes ...")
for (node in nodes.values) {
node.shutDown()
if (node.rpcProxy) {
log.info("Shutting down RPC proxy ...")
try {
val rpcProxyPortNo = node.config.nodeInterface.rpcProxy
val pid = Files.lines(tmpDirectory / "rpcProxy-pid-$rpcProxyPortNo").findFirst().get()
// TODO: consider generic implementation to support non *nix platforms
Command(listOf("kill", "-9", "$pid")).run()
(tmpDirectory / "rpcProxy-pid-$rpcProxyPortNo").deleteIfExists()
}
catch (e: Exception) {
log.warn("Unable to locate PID file: ${e.message}")
}
}
}
cleanup()
if (isDoormanNMSRunning) {
log.info("Shutting down R3 Corda NMS server ...")
doormanNMS.kill()
}
if (System.getProperty("DISABLE_CLEANUP") == null) // useful for re-starting and troubleshooting failure issues
cleanup()
}
fun use(action: (Network) -> Unit) {
@ -295,7 +475,7 @@ class Network private constructor(
val log = contextLogger()
const val CLEANUP_ON_ERROR = false
fun new(timeout: Duration = 2.minutes
): Builder = Builder(timeout)
fun new(type: Distribution.Type = Distribution.Type.CORDA, timeout: Duration = 2.minutes
): Builder = Builder(type, timeout)
}
}

View File

@ -11,6 +11,7 @@
package net.corda.behave.node
import net.corda.behave.file.stagingRoot
import net.corda.core.CordaRuntimeException
import net.corda.core.internal.copyTo
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
@ -24,6 +25,11 @@ import java.nio.file.Path
*/
class Distribution private constructor(
/**
* The distribution type of Corda: Open Source or R3 Corda (Enterprise)
*/
val type: Type,
/**
* The version string of the Corda distribution.
*/
@ -65,6 +71,16 @@ class Distribution private constructor(
*/
val networkBootstrapper: Path = path / "network-bootstrapper.jar"
/**
* The path to the doorman jar (R3 Corda only).
*/
val doormanJar: Path = path / "doorman.jar"
/**
* The path to the DB migration jar (R3 Corda only).
*/
val dbMigrationJar: Path = nodePrefix / version / "dbmigration.jar"
/**
* Ensure that the distribution is available on disk.
*/
@ -89,6 +105,11 @@ class Distribution private constructor(
*/
override fun toString() = "Corda(version = $version, path = $cordaJar)"
enum class Type {
CORDA,
R3_CORDA
}
companion object {
private val log = contextLogger()
@ -97,38 +118,46 @@ class Distribution private constructor(
private val nodePrefix = stagingRoot / "corda"
val MASTER = fromJarFile("corda-master")
val MASTER = fromJarFile(Type.CORDA, "corda-master")
val R3_MASTER = fromJarFile(Type.R3_CORDA, "r3corda-master")
/**
* Get representation of a Corda distribution from Artifactory based on its version string.
* @param type The Corda distribution type.
* @param version The version of the Corda distribution.
*/
fun fromArtifactory(version: String): Distribution {
val url = URL("https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases/net/corda/corda/$version/corda-$version.jar")
fun fromArtifactory(type: Type, version: String): Distribution {
val url =
when (type) {
Type.CORDA -> URL("https://ci-artifactory.corda.r3cev.com/artifactory/corda-releases/net/corda/corda/$version/corda-$version.jar")
Type.R3_CORDA -> URL("https://ci-artifactory.corda.r3cev.com/artifactory/r3-corda-releases/com/r3/corda/corda/$version/corda-$version.jar")
}
log.info("Artifactory URL: $url\n")
val distribution = Distribution(version, url = url)
val distribution = Distribution(type, version, url = url)
distributions.add(distribution)
return distribution
}
/**
* Get representation of a Corda distribution based on its version string and fat JAR path.
* @param type The Corda distribution type.
* @param version The version of the Corda distribution.
* @param jarFile The path to the Corda fat JAR.
*/
fun fromJarFile(version: String, jarFile: Path? = null): Distribution {
val distribution = Distribution(version, file = jarFile)
fun fromJarFile(type: Type, version: String, jarFile: Path? = null): Distribution {
val distribution = Distribution(type, version, file = jarFile)
distributions.add(distribution)
return distribution
}
/**
* Get Corda distribution from a Docker image file.
* @param type The Corda distribution type.
* @param baseImage The name (eg. corda) of the Corda distribution.
* @param imageTag The version (github commit id or corda version) of the Corda distribution.
*/
fun fromDockerImage(baseImage: String, imageTag: String): Distribution {
val distribution = Distribution(version = imageTag, baseImage = baseImage)
fun fromDockerImage(type: Type, baseImage: String, imageTag: String): Distribution {
val distribution = Distribution(type, version = imageTag, baseImage = baseImage)
distributions.add(distribution)
return distribution
}
@ -139,8 +168,11 @@ class Distribution private constructor(
*/
fun fromVersionString(version: String): Distribution = when (version) {
"master" -> MASTER
"corda-3.0" -> fromArtifactory(version)
else -> fromJarFile(version)
"r3-master" -> R3_MASTER
"corda-3.0" -> fromArtifactory(Type.CORDA, version)
"corda-3.1" -> fromArtifactory(Type.CORDA, version)
"R3.CORDA-3.0.0-DEV-PREVIEW-3" -> fromArtifactory(Type.R3_CORDA, version)
else -> distributions.firstOrNull { it.version == version } ?: throw CordaRuntimeException("Unable to locate Corda distribution for version: $version")
}
}
}

View File

@ -18,16 +18,20 @@ import net.corda.behave.file.stagingRoot
import net.corda.behave.monitoring.PatternWatch
import net.corda.behave.node.configuration.*
import net.corda.behave.process.JarCommand
import net.corda.behave.process.JarCommandWithMain
import net.corda.behave.service.Service
import net.corda.behave.service.ServiceSettings
import net.corda.behave.service.proxy.CordaRPCProxyClient
import net.corda.behave.ssh.MonitoringSSHClient
import net.corda.behave.ssh.SSHClient
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.core.internal.createDirectories
import net.corda.core.internal.div
import net.corda.core.internal.exists
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.minutes
import net.corda.core.utilities.loggerFor
import net.corda.core.utilities.seconds
import org.apache.commons.io.FileUtils
@ -42,7 +46,9 @@ import java.util.concurrent.CountDownLatch
class Node(
val config: Configuration,
private val rootDirectory: Path = currentDirectory,
private val settings: ServiceSettings = ServiceSettings()
private val settings: ServiceSettings = ServiceSettings(),
val rpcProxy: Boolean = false,
val networkType: Distribution.Type
) {
private val log = loggerFor<Node>()
@ -89,16 +95,40 @@ class Node(
log.info("Configuring {} ...", this)
serviceDependencies.addAll(config.database.type.dependencies(config))
config.distribution.ensureAvailable()
config.writeToFile(rootDirectory / "${config.name}_node.conf")
if (networkType == Distribution.Type.CORDA) {
config.writeToFile(rootDirectory / "${config.name}_node.conf")
}
else {
val nodeDirectory = (rootDirectory / config.name).createDirectories()
config.writeToFile(nodeDirectory / "node.conf")
}
installApps()
}
private fun initialiseDatabase(database: DatabaseConfiguration) {
val driversDir = runtimeDirectory / "drivers"
log.info("Creating directory for drivers: $driversDir")
driversDir.toFile().mkdirs()
log.info("Initialising database for R3 Corda node: $database")
val command = JarCommandWithMain(listOf(config.distribution.dbMigrationJar, rootDirectory / "libs" / database.type.driverJar!!),
"com.r3.corda.dbmigration.DBMigration",
arrayOf("--base-directory", "$runtimeDirectory", "--execute-migration"),
runtimeDirectory, 2.minutes)
command.run()
}
fun start(): Boolean {
if (!startDependencies()) {
return false
}
log.info("Starting {} ...", this)
return try {
// initialise database via DB migration tool
if (config.distribution.type == Distribution.Type.R3_CORDA &&
config.database.type != DatabaseType.H2) {
initialiseDatabase(config.database)
}
// launch node itself
command.start()
isStarted = true
true
@ -182,6 +212,20 @@ class Node(
return result ?: error("Failed to run RPC action")
}
fun <T> http(action: (CordaRPCOps) -> T): T {
val address = config.nodeInterface
val targetHost = NetworkHostAndPort(address.host, address.rpcProxy)
log.info("Establishing HTTP connection to ${targetHost.host} on port ${targetHost.port} ...")
try {
return action(CordaRPCProxyClient(targetHost))
}
catch (e: Exception) {
log.warn("Failed to invoke http endpoint: ", e)
e.printStackTrace()
error("Failed to run http action")
}
}
override fun toString(): String {
return "Node(name = ${config.name}, version = ${config.distribution.version})"
}
@ -248,6 +292,8 @@ class Node(
private var notaryType = NotaryType.NONE
private var compatibilityZoneURL: String? = null
private val issuableCurrencies = mutableListOf<String>()
private var location: String = "London"
@ -262,6 +308,10 @@ class Node(
private var timeout = Duration.ofSeconds(60)
private var rpcProxy = false
var networkType = distribution.type
fun withName(newName: String): Builder {
name = newName
return this
@ -269,6 +319,7 @@ class Node(
fun withDistribution(newDistribution: Distribution): Builder {
distribution = newDistribution
networkType = distribution.type
return this
}
@ -282,6 +333,16 @@ class Node(
return this
}
fun withNetworkMap(newCompatibilityZoneURL: String?): Builder {
compatibilityZoneURL = newCompatibilityZoneURL
return this
}
fun withNetworkType(newNetworkType: Distribution.Type): Builder {
networkType = newNetworkType
return this
}
fun withIssuableCurrencies(vararg currencies: String): Builder {
issuableCurrencies.addAll(currencies)
return this
@ -318,9 +379,18 @@ class Node(
return this
}
fun withRPCProxy(withRPCProxy: Boolean): Builder {
rpcProxy = withRPCProxy
return this
}
fun build(): Node {
val name = name ?: error("Node name not set")
val directory = directory ?: error("Runtime directory not set")
val compatibilityZoneURL =
if (networkType == Distribution.Type.R3_CORDA)
compatibilityZoneURL ?: "http://localhost:1300"
else null
return Node(
Configuration(
name,
@ -335,11 +405,14 @@ class Node(
),
configElements = *arrayOf(
NotaryConfiguration(notaryType),
NetworkMapConfiguration(compatibilityZoneURL),
CurrencyConfiguration(issuableCurrencies)
)
),
directory,
ServiceSettings(timeout)
ServiceSettings(timeout),
rpcProxy = rpcProxy,
networkType = networkType
)
}

View File

@ -36,7 +36,7 @@ class Configuration(
vararg configElements: ConfigurationTemplate
) {
private val developerMode = true
private val developerMode = (distribution.type == Distribution.Type.CORDA)
val cordaX500Name: CordaX500Name by lazy({
CordaX500Name(name, location, country)

View File

@ -18,6 +18,7 @@ data class NetworkInterface(
val sshPort: Int = getPort(2222 + nodeIndex),
val p2pPort: Int = getPort(12001 + (nodeIndex * 5)),
val rpcPort: Int = getPort(12002 + (nodeIndex * 5)),
val rpcProxy: Int = getPort(13002 + (nodeIndex * 5)),
val rpcAdminPort: Int = getPort(12003 + (nodeIndex * 5)),
val webPort: Int = getPort(12004 + (nodeIndex * 5)),
val dbPort: Int = getPort(12005 + (nodeIndex * 5)),

View File

@ -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 {
""
}
}
}

View File

@ -10,7 +10,7 @@
package net.corda.behave.node.configuration
class NotaryConfiguration(private val notaryType: NotaryType = NotaryType.NONE) : ConfigurationTemplate() {
class NotaryConfiguration(val notaryType: NotaryType = NotaryType.NONE) : ConfigurationTemplate() {
override val config: (Configuration) -> String
get() = {

View File

@ -14,7 +14,7 @@ import java.nio.file.Path
import java.time.Duration
class JarCommand(
jarFile: Path,
val jarFile: Path,
arguments: Array<out String>,
directory: Path,
timeout: Duration,

View File

@ -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()
}
}
}

View File

@ -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"
}
}

View File

@ -0,0 +1,3 @@
minimumPlatformVersion = 1
maxMessageSize = 10485760
maxTransactionSize = 10485760

View File

@ -0,0 +1,7 @@
notaries : [{
notaryNodeInfoFile: "../Notary/notary-node-info"
validating: false
}]
minimumPlatformVersion = 1
maxMessageSize = 10485760
maxTransactionSize = 10485760

View File

@ -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
#}

View 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
}

View File

@ -12,6 +12,7 @@ package net.corda.behave.scenarios
import cucumber.api.java.After
import net.corda.behave.network.Network
import net.corda.behave.node.Distribution
import net.corda.behave.node.Node
import net.corda.core.messaging.CordaRPCOps
import org.assertj.core.api.Assertions.assertThat
@ -50,9 +51,15 @@ class ScenarioState {
// Network is already running
return
}
val networkBuilder = Network.new()
// Corda Network will be configured as R3 Corda (with Doorman/NMS) if any Node uses an R3.Corda distribution
val r3CordaNode = nodes.find { it.networkType == Distribution.Type.R3_CORDA }
val networkType = if (r3CordaNode != null) Distribution.Type.R3_CORDA else Distribution.Type.CORDA
log.info("Corda network type: $networkType")
val networkBuilder = Network.new(networkType)
for (node in nodes) {
networkBuilder.addNode(node)
networkBuilder.addNode(node.withNetworkType(networkType))
}
network = networkBuilder.generate()
network?.start()
@ -72,6 +79,14 @@ class ScenarioState {
}
}
inline fun <T> withClientProxy(nodeName: String, crossinline action: (CordaRPCOps) -> T): T {
withNetwork {
return node(nodeName).http {
action(it)
}
}
}
@After
fun stopNetwork() {
val network = network ?: return

View File

@ -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")
}
})
}
}

View File

@ -39,6 +39,18 @@ class ConfigurationSteps : StepsBlock {
?: error("Unknown notary type '$notaryType'"))
}
Given<String, String>("^a node (\\w+) of version ([^ ]+) with proxy$") { name, version ->
node(name).withRPCProxy(true)
.withDistribution(Distribution.fromVersionString(version))
}
Given<String, String, String>("^a (\\w+) notary node (\\w+) of version ([^ ]+) with proxy$") { notaryType, name, version ->
node(name).withRPCProxy(true)
.withDistribution(Distribution.fromVersionString(version))
.withNotaryType(notaryType.toNotaryType()
?: error("Unknown notary type '$notaryType'"))
}
Given<String, String, String>("^a (\\w+) notary (\\w+) of version ([^ ]+)$") { type, name, version ->
node(name)
.withDistribution(Distribution.fromVersionString(version))
@ -70,7 +82,5 @@ class ConfigurationSteps : StepsBlock {
Given<String, String>("^node (\\w+) has app installed: (.+)$") { name, app ->
node(name).withApp(app)
}
}
}

View File

@ -10,7 +10,7 @@ Feature: Cash - Issuable Currencies
Examples:
| Node-Version |
| master |
| r3-master |
Scenario Outline: Node has an issuable currency
Given a node PartyA of version <Node-Version>
@ -21,7 +21,7 @@ Feature: Cash - Issuable Currencies
Examples:
| Node-Version |
| master |
| r3-master |
Scenario Outline: Node can issue a currency
Given a node PartyA of version <Node-Version>
@ -32,4 +32,4 @@ Feature: Cash - Issuable Currencies
Examples:
| Node-Version |
| master |
| r3-master |

View File

@ -10,7 +10,8 @@ Feature: Database - Connection
Examples:
| Node-Version | Database-Type |
| master | H2 |
| r3-master | H2 |
# To run this scenario using postgreSQL you must ensure that Docker is running locally
# | master | postgreSQL |
# To run this scenario using other DB providers you must ensure that Docker is running locally
# | r3-master | postgreSQL |
# | r3-master | SQL Server |

View File

@ -12,7 +12,7 @@ Feature: Startup Information - Logging
Examples:
| Node-Version | Database-Type |
| master | H2 |
| r3-master | H2 |
Scenario Outline: Node shows database details on startup
Given a node PartyA of version <Node-Version>
@ -22,7 +22,7 @@ Feature: Startup Information - Logging
Examples:
| Node-Version | Database-Type |
| master | H2 |
| r3-master | H2 |
Scenario Outline: Node shows version information on startup
Given a node PartyA of version <Node-Version>
@ -30,5 +30,19 @@ Feature: Startup Information - Logging
And node PartyA is on release version <Release-Version>
Examples:
| Node-Version | Platform-Version | Release-Version |
| master | 4 | corda-4.0-snapshot |
| Node-Version | Platform-Version | Release-Version |
| r3-master | 4 | R3.CORDA-3.0-SNAPSHOT |
Scenario Outline: Start-up a simple 3 node network with a non validating notary
Given a node PartyA of version <Node1-Version>
And a node PartyB of version <Node2-Version>
And a node PartyC of version <Node3-Version>
And a nonvalidating notary Notary of version <Notary-Version>
When the network is ready
Then user can retrieve logging information for node PartyA
And user can retrieve logging information for node PartyB
And user can retrieve logging information for node PartyC
Examples:
| Node1-Version | Node2-Version | Node3-Version | Notary-Version |
| r3-master | r3-master | r3-master | r3-master |

View File

@ -14,7 +14,7 @@ cd ${BUILD_DIR}
../../gradlew behaveJar
BEHAVE_JAR=$(ls build/libs/corda-behave-*.jar | tail -n1)
STAGING_ROOT=~/staging
STAGING_ROOT=~/staging
# startup
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/startup/logging.feature
@ -23,4 +23,4 @@ java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.s
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/cash/currencies.feature
# database
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/cash/currencies.feature
java -DSTAGING_ROOT=${STAGING_ROOT} -jar ${BEHAVE_JAR} --glue net.corda.behave.scenarios -path ./src/scenario/resources/features/database/connection.feature

View File

@ -0,0 +1,44 @@
package net.corda.behave.process
import net.corda.behave.node.Distribution
import net.corda.core.CordaRuntimeException
import net.corda.core.internal.div
import net.corda.core.utilities.minutes
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.nio.file.Paths
class DBMigrationToolTests {
/**
* Commands used to perform Database initialisation and migration as per:
* http://docs.corda.r3.com/website/releases/docs_head/api-persistence.html#database-migration
*/
// Set corresponding Java properties to point to valid Corda node configurations
// eg. -DNODE_DIR=<location of node configuration directory> -DJDBC_DRIVER=postgresql-42.1.4.jar
private val nodeRunDir = Paths.get(System.getProperty("NODE_DIR") ?: throw CordaRuntimeException("Please set NODE_DIR to point to valid Node configuration"))
private val jdbcDriver = nodeRunDir / ".." / "libs" / (System.getProperty("JDBC_DRIVER") ?: throw CordaRuntimeException("Please set JDBC_DRIVER to point to valid JDBC driver jar file located under $nodeRunDir\\..\\libs"))
private val migrationToolMain = "com.r3.corda.dbmigration.DBMigration"
@Test
fun `dry run migration`() {
println(nodeRunDir)
val command = JarCommandWithMain(listOf(Distribution.R3_MASTER.dbMigrationJar, jdbcDriver),
migrationToolMain,
arrayOf("--base-directory", "$nodeRunDir", "--dry-run"),
nodeRunDir, 2.minutes)
assertThat(command.run()).isEqualTo(0)
}
@Test
fun `execute migration`() {
println(nodeRunDir)
val command = JarCommandWithMain(listOf(Distribution.R3_MASTER.dbMigrationJar, jdbcDriver),
migrationToolMain,
arrayOf("--base-directory", "$nodeRunDir", "--execute-migration"),
nodeRunDir, 2.minutes)
assertThat(command.run()).isEqualTo(0)
}
}

View File

@ -0,0 +1,110 @@
package net.corda.behave.process
import net.corda.behave.file.currentDirectory
import net.corda.behave.file.doormanConfigDirectory
import net.corda.behave.node.Distribution
import net.corda.core.CordaRuntimeException
import net.corda.core.internal.div
import net.corda.core.utilities.minutes
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.nio.file.Paths
class DoormanCommandTests {
/**
* Commands that reproduce the setting up of an R3 Corda Network as per instructions in:
* https://github.com/corda/enterprise/blob/master/network-management/README.md
*/
private val source = doormanConfigDirectory
private val doormanRunDir = currentDirectory / "build" / "runs" / "doorman"
// Set corresponding Java properties to point to valid Corda node configurations
// eg. -DNOTARY_NODE_DIR=<location of notary node configuration directory>
private val notaryRunDir = Paths.get(System.getProperty("NOTARY_NODE_DIR") ?: throw CordaRuntimeException("Please set NOTARY_NODE_DIR to point to valid Notary node configuration"))
private val nodeRunDir = Paths.get(System.getProperty("NODE_DIR") ?: throw CordaRuntimeException("Please set NODE_DIR to point to valid Node configuration"))
@Test
fun `step 1 - create key stores for local signer`() {
println(doormanRunDir)
source.toFile().copyRecursively(doormanRunDir.toFile(), true)
// ROOT
val commandRoot = JarCommand(Distribution.R3_MASTER.doormanJar,
arrayOf("--config-file", "$doormanConfigDirectory/node-init.conf", "--mode", "ROOT_KEYGEN", "--trust-store-password", "password"),
doormanRunDir, 1.minutes)
assertThat(commandRoot.run()).isEqualTo(0)
// CA
val commandCA = JarCommand(Distribution.R3_MASTER.doormanJar,
arrayOf("--config-file", "$doormanConfigDirectory/node-init.conf", "--mode", "CA_KEYGEN"),
doormanRunDir, 1.minutes)
assertThat(commandCA.run()).isEqualTo(0)
}
@Test
fun `step 2 - start doorman service for notary registration`() {
println(doormanRunDir)
val command = JarCommand(Distribution.R3_MASTER.doormanJar,
arrayOf("--config-file", "$doormanConfigDirectory/node-init.conf"),
doormanRunDir, 10.minutes)
assertThat(command.run()).isEqualTo(0)
}
@Test
fun `step 3 - create notary node and register with the doorman`() {
println(notaryRunDir)
val command = JarCommand(Distribution.R3_MASTER.cordaJar,
arrayOf("--initial-registration",
"--base-directory", "$notaryRunDir",
"--network-root-truststore", "../doorman/certificates/distribute-nodes/network-root-truststore.jks",
"--network-root-truststore-password", "password"),
notaryRunDir, 2.minutes)
assertThat(command.run()).isEqualTo(0)
}
@Test
fun `step 4 - generate node info files for notary nodes`() {
println(notaryRunDir)
val command = JarCommand(Distribution.R3_MASTER.cordaJar,
arrayOf("--just-generate-node-info",
"--base-directory", "$notaryRunDir"),
doormanRunDir, 1.minutes)
assertThat(command.run()).isEqualTo(0)
}
// step 5 Add notary identities to the network parameters
// (already configured in template network parameters file)
@Test
fun `step 6 - load initial network parameters file for network map service`() {
println(doormanRunDir)
val command = JarCommand(Distribution.R3_MASTER.doormanJar,
arrayOf("--config-file", "$doormanRunDir/node.conf", "--set-network-parameters", "$doormanRunDir/network-parameters.conf"),
doormanRunDir, 1.minutes)
assertThat(command.run()).isEqualTo(0)
}
@Test
fun `step 7 - Start a fully configured Doorman NMS`() {
println(doormanRunDir)
val command = JarCommand(Distribution.R3_MASTER.doormanJar,
arrayOf("--config-file", "$doormanRunDir/node.conf"),
doormanRunDir, 1.minutes)
assertThat(command.run()).isEqualTo(0)
}
@Test
fun `step 8 - initial registration of network participant nodes`() {
println(nodeRunDir)
val command = JarCommand(Distribution.R3_MASTER.cordaJar,
arrayOf("--initial-registration",
"--network-root-truststore", "../doorman/certificates/distribute-nodes/network-root-truststore.jks",
"--network-root-truststore-password", "password",
"--base-directory", "$nodeRunDir"),
doormanRunDir, 2.minutes)
assertThat(command.run()).isEqualTo(0)
}
}

View File

@ -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()
}
}

View File

@ -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")
}
}

View File

@ -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()
}
}
}

View File

@ -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()
}
}
}

View File

@ -11,13 +11,79 @@
package net.corda.behave.network
import net.corda.behave.database.DatabaseType
import net.corda.behave.node.Distribution
import net.corda.behave.node.configuration.NotaryType
import net.corda.core.utilities.hours
import net.corda.core.utilities.seconds
import org.junit.Ignore
import org.junit.Test
import java.time.Duration
class NetworkTests {
@Ignore
@Test
fun `OS Corda network of single node with RPC proxy can be spun up`() {
val distribution = Distribution.MASTER
val network = Network
.new()
.addNode(name = "Foo", distribution = distribution, notaryType = NotaryType.NON_VALIDATING, withRPCProxy = true)
.generate()
network.use {
it.waitUntilRunning(300.seconds)
it.keepAlive(300.seconds)
it.signal()
}
}
@Ignore
@Test
fun `R3 Corda network of single node with RPC proxy can be spun up`() {
val network = Network
.new(Distribution.Type.R3_CORDA)
.addNode(name = "Notary", distribution = Distribution.R3_MASTER, notaryType = NotaryType.NON_VALIDATING, withRPCProxy = true)
.generate()
network.use {
it.waitUntilRunning(1.hours)
it.keepAlive(1.hours)
it.signal()
}
}
@Ignore
@Test
fun `Mixed OS Corda network of two nodes (with an RPC proxy each) and a non-validating notary can be spun up`() {
// Note: this test exercises the NetworkBootstrapper to setup a local network
val distribution = Distribution.MASTER
val network = Network
.new()
.addNode(name = "EntityA", distribution = Distribution.MASTER, withRPCProxy = true)
.addNode(name = "EntityB", distribution = Distribution.R3_MASTER, withRPCProxy = true)
.addNode(name = "Notary", distribution = distribution, notaryType = NotaryType.NON_VALIDATING)
.generate()
network.use {
it.waitUntilRunning(Duration.ofDays(1))
it.keepAlive(Duration.ofDays(1))
}
}
@Ignore
@Test
fun `Mixed R3 Corda network of two nodes (with an RPC proxy each) and a non-validating notary can be spun up`() {
// Note: this test exercises the Doorman / Notary / NMS setup sequence
val distribution = Distribution.R3_MASTER
val network = Network
.new()
.addNode(name = "EntityA", distribution = Distribution.R3_MASTER, withRPCProxy = true)
.addNode(name = "EntityB", distribution = Distribution.MASTER, withRPCProxy = true)
.addNode(name = "Notary", distribution = distribution, notaryType = NotaryType.NON_VALIDATING)
.generate()
network.use {
it.waitUntilRunning(Duration.ofDays(1))
it.keepAlive(Duration.ofDays(1))
}
}
@Ignore
@Test
fun `network of two nodes can be spun up`() {

View File

@ -31,7 +31,7 @@ class CommandTests {
@Test
fun `output stream for command can be observed`() {
val subscriber = TestSubscriber<String>()
val exitCode = Command(listOf("ls", "/")).use(subscriber) { _, output ->
val exitCode = Command(listOf("ls", "/")).use(subscriber) { _, _ ->
subscriber.awaitTerminalEvent()
subscriber.assertCompleted()
subscriber.assertNoErrors()

View File

@ -23,15 +23,15 @@ import java.util.*
class CashSelectionOracleImpl : AbstractCashSelection(maxRetries = 16, retrySleep = 1000, retryCap = 5000) {
companion object {
val JDBC_DRIVER_NAME = "Oracle JDBC driver"
const val JDBC_DRIVER_NAME = "Oracle JDBC driver"
private val log = contextLogger()
}
override fun isCompatible(metaData: DatabaseMetaData): Boolean {
return metaData.driverName == JDBC_DRIVER_NAME
return metaData.driverName.startsWith(JDBC_DRIVER_NAME, ignoreCase = true)
}
override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME"
override fun toString() = "${this::class.qualifiedName} for '$JDBC_DRIVER_NAME'"
override fun executeQuery(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?,
onlyFromIssuerParties: Set<AbstractParty>, withIssuerRefs: Set<OpaqueBytes>, withResultSet: (ResultSet) -> Boolean): Boolean {

View File

@ -28,7 +28,7 @@ class CashSelectionPostgreSQLImpl : AbstractCashSelection() {
}
override fun isCompatible(metadata: DatabaseMetaData): Boolean {
return metadata.driverName == JDBC_DRIVER_NAME
return metadata.driverName.startsWith(JDBC_DRIVER_NAME, ignoreCase = true)
}
override fun toString() = "${this::class.qualifiedName} for '$JDBC_DRIVER_NAME'"

View File

@ -28,15 +28,15 @@ import java.util.*
class CashSelectionSQLServerImpl : AbstractCashSelection(maxRetries = 16, retrySleep = 1000, retryCap = 5000) {
companion object {
val JDBC_DRIVER_NAME = "Microsoft JDBC Driver 6.2 for SQL Server"
const val JDBC_DRIVER_NAME = "Microsoft JDBC Driver"
private val log = contextLogger()
}
override fun isCompatible(metaData: DatabaseMetaData): Boolean {
return metaData.driverName == JDBC_DRIVER_NAME
return metaData.driverName.startsWith(JDBC_DRIVER_NAME, ignoreCase = true)
}
override fun toString() = "${this::class.java} for $JDBC_DRIVER_NAME"
override fun toString() = "${this::class.qualifiedName} for '$JDBC_DRIVER_NAME'"
override fun executeQuery(connection: Connection, amount: Amount<Currency>, lockId: UUID, notary: Party?,
onlyFromIssuerParties: Set<AbstractParty>,

View File

@ -32,11 +32,24 @@ sourceSets {
srcDir file('../testing/test-utils/src/main/resources')
}
}
scenarioTest {
kotlin {
compileClasspath += main.output + test.output
runtimeClasspath += main.output + test.output
srcDir file('src/system-test/scenario/kotlin')
}
resources {
srcDir 'src/system-test/scenario/resources'
}
}
}
configurations {
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
scenarioTestCompile.extendsFrom testCompile
scenarioTestRuntime.extendsFrom testRuntime
}
dependencies {
@ -69,6 +82,7 @@ dependencies {
// Test dependencies
testCompile project(':node-driver')
scenarioTestCompile project(path: ":experimental:behave", configuration: 'testArtifacts')
testCompile "junit:junit:$junit_version"
testCompile "org.assertj:assertj-core:${assertj_version}"
}
@ -147,6 +161,16 @@ task integrationTest(type: Test, dependsOn: []) {
classpath = sourceSets.integrationTest.runtimeClasspath
}
task scenarioTest(type: Test, dependsOn: []) {
testClassesDirs = sourceSets.scenarioTest.output.classesDirs
classpath = sourceSets.scenarioTest.runtimeClasspath
}
task scenarioJar(type: Jar, dependsOn: classes) {
classifier "behave-test"
from sourceSets.scenarioTest.output
}
publishing {
publications {
simmvaluationdemo(MavenPublication) {
@ -155,6 +179,7 @@ publishing {
artifact sourceJar
artifact javadocJar
artifact scenarioJar
}
}
}

View File

@ -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()
}

View File

@ -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")
}
}

View File

@ -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)
}
}
}
}

View File

@ -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 |

View File

@ -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

View File

@ -13,4 +13,4 @@ cd ${BEHAVE_DIR}
../../gradlew behaveJar
# QA interoperability
java -jar ${BEHAVE_DIR}/build/libs/corda-behave.jar -d --glue net.corda.behave.scenarios -path ${R3CORDA_HOME}/testing/qa/behave/compatibility/resources/features/qa-interop-testing.feature
java -jar ${BEHAVE_DIR}/build/libs/corda-behave.jar -d --glue net.corda.behave.scenarios -path ${R3CORDA_HOME}/testing/qa/behave/compatibility/resources/features/interoperability.feature

View File

@ -14,4 +14,4 @@ cd ${BEHAVE_DIR}
../../gradlew behaveJar
# QA functional
java -jar ${BEHAVE_DIR}/build/libs/corda-behave.jar -d --glue net.corda.behave.scenarios -path ${R3CORDA_HOME}/testing/qa/behave/functional/resources/features/qa-functional-testing.feature
java -jar ${BEHAVE_DIR}/build/libs/corda-behave.jar -d --glue net.corda.behave.scenarios -path ${R3CORDA_HOME}/testing/qa/behave/functional/resources/features/functional.feature