diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 7871abe352..73ece5801a 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -12,9 +12,11 @@
+
+
@@ -73,6 +75,8 @@
+
+
@@ -169,6 +173,8 @@
+
+
diff --git a/experimental/behave/build.gradle b/experimental/behave/build.gradle
index 434f737edc..3eba8624bc 100644
--- a/experimental/behave/build.gradle
+++ b/experimental/behave/build.gradle
@@ -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
+}
diff --git a/experimental/behave/prepare.sh b/experimental/behave/prepare.sh
index bdd41c4a1b..1827506bde 100755
--- a/experimental/behave/prepare.sh
+++ b/experimental/behave/prepare.sh
@@ -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
+
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseType.kt b/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseType.kt
index 392fba1822..5c52237362 100644
--- a/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseType.kt
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/database/DatabaseType.kt
@@ -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
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/database/configuration/SqlServerConfigurationTemplate.kt b/experimental/behave/src/main/kotlin/net/corda/behave/database/configuration/SqlServerConfigurationTemplate.kt
new file mode 100644
index 0000000000..6b544c632e
--- /dev/null
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/database/configuration/SqlServerConfigurationTemplate.kt
@@ -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}"
+ |}
+ """
+ }
+
+}
\ No newline at end of file
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/file/FileUtilities.kt b/experimental/behave/src/main/kotlin/net/corda/behave/file/FileUtilities.kt
index 81229690a4..f0edd1a84a 100644
--- a/experimental/behave/src/main/kotlin/net/corda/behave/file/FileUtilities.kt
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/file/FileUtilities.kt
@@ -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")
\ No newline at end of file
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/network/Network.kt b/experimental/behave/src/main/kotlin/net/corda/behave/network/Network.kt
index e4c84dd7fb..21668c964b 100644
--- a/experimental/behave/src/main/kotlin/net/corda/behave/network/Network.kt
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/network/Network.kt
@@ -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 = emptyList()
+ issuableCurrencies: List = 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-.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-.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)
}
}
\ No newline at end of file
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/Distribution.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/Distribution.kt
index c2f23e5e8b..eff9eb020d 100644
--- a/experimental/behave/src/main/kotlin/net/corda/behave/node/Distribution.kt
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/Distribution.kt
@@ -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")
}
}
}
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/Node.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/Node.kt
index b7d132a9b4..475ae383bd 100644
--- a/experimental/behave/src/main/kotlin/net/corda/behave/node/Node.kt
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/Node.kt
@@ -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()
@@ -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 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()
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
)
}
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/Configuration.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/Configuration.kt
index 494b595541..42bb277775 100644
--- a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/Configuration.kt
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/Configuration.kt
@@ -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)
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NetworkInterface.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NetworkInterface.kt
index e358da148f..265cab404f 100644
--- a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NetworkInterface.kt
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NetworkInterface.kt
@@ -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)),
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NetworkMapConfiguration.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NetworkMapConfiguration.kt
new file mode 100644
index 0000000000..e6c5ff632e
--- /dev/null
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NetworkMapConfiguration.kt
@@ -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 {
+ ""
+ }
+ }
+}
\ No newline at end of file
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NotaryConfiguration.kt b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NotaryConfiguration.kt
index 63a82ece68..42ba7c11ee 100644
--- a/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NotaryConfiguration.kt
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/node/configuration/NotaryConfiguration.kt
@@ -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() = {
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/process/JarCommand.kt b/experimental/behave/src/main/kotlin/net/corda/behave/process/JarCommand.kt
index 299595dbaf..f72cf88f67 100644
--- a/experimental/behave/src/main/kotlin/net/corda/behave/process/JarCommand.kt
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/process/JarCommand.kt
@@ -14,7 +14,7 @@ import java.nio.file.Path
import java.time.Duration
class JarCommand(
- jarFile: Path,
+ val jarFile: Path,
arguments: Array,
directory: Path,
timeout: Duration,
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/process/JarCommandWithMain.kt b/experimental/behave/src/main/kotlin/net/corda/behave/process/JarCommandWithMain.kt
new file mode 100644
index 0000000000..a2c2be9eba
--- /dev/null
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/process/JarCommandWithMain.kt
@@ -0,0 +1,36 @@
+package net.corda.behave.process
+
+import java.nio.file.Path
+import java.time.Duration
+
+class JarCommandWithMain(
+ jarFiles: List,
+ mainClass: String,
+ arguments: Array,
+ 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()
+ }
+
+ }
+
+}
\ No newline at end of file
diff --git a/experimental/behave/src/main/kotlin/net/corda/behave/service/database/SqlServerService.kt b/experimental/behave/src/main/kotlin/net/corda/behave/service/database/SqlServerService.kt
new file mode 100644
index 0000000000..4e3601a164
--- /dev/null
+++ b/experimental/behave/src/main/kotlin/net/corda/behave/service/database/SqlServerService.kt
@@ -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"
+
+ }
+
+}
\ No newline at end of file
diff --git a/experimental/behave/src/main/resources/doorman/network-parameters-without-notary.conf b/experimental/behave/src/main/resources/doorman/network-parameters-without-notary.conf
new file mode 100644
index 0000000000..1f47b7f021
--- /dev/null
+++ b/experimental/behave/src/main/resources/doorman/network-parameters-without-notary.conf
@@ -0,0 +1,3 @@
+minimumPlatformVersion = 1
+maxMessageSize = 10485760
+maxTransactionSize = 10485760
diff --git a/experimental/behave/src/main/resources/doorman/network-parameters.conf b/experimental/behave/src/main/resources/doorman/network-parameters.conf
new file mode 100644
index 0000000000..a34049e6cb
--- /dev/null
+++ b/experimental/behave/src/main/resources/doorman/network-parameters.conf
@@ -0,0 +1,7 @@
+notaries : [{
+ notaryNodeInfoFile: "../Notary/notary-node-info"
+ validating: false
+ }]
+ minimumPlatformVersion = 1
+ maxMessageSize = 10485760
+ maxTransactionSize = 10485760
diff --git a/experimental/behave/src/main/resources/doorman/node-init.conf b/experimental/behave/src/main/resources/doorman/node-init.conf
new file mode 100644
index 0000000000..dbc3935d1e
--- /dev/null
+++ b/experimental/behave/src/main/resources/doorman/node-init.conf
@@ -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
+#}
diff --git a/experimental/behave/src/main/resources/doorman/node.conf b/experimental/behave/src/main/resources/doorman/node.conf
new file mode 100644
index 0000000000..7c229ca01e
--- /dev/null
+++ b/experimental/behave/src/main/resources/doorman/node.conf
@@ -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
+}
\ No newline at end of file
diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt
index 0068b9bb63..fc3b14c423 100644
--- a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt
+++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/ScenarioState.kt
@@ -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 withClientProxy(nodeName: String, crossinline action: (CordaRPCOps) -> T): T {
+ withNetwork {
+ return node(nodeName).http {
+ action(it)
+ }
+ }
+ }
+
@After
fun stopNetwork() {
val network = network ?: return
diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Substeps.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Substeps.kt
index b9b7ad8044..1fa04e4d4f 100644
--- a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Substeps.kt
+++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/helpers/Substeps.kt
@@ -30,4 +30,14 @@ abstract class Substeps(protected val state: ScenarioState) {
}
})
}
+
+ protected fun withClientProxy(nodeName: String, action: ScenarioState.(CordaRPCOps) -> T): T {
+ return state.withClientProxy(nodeName, {
+ return@withClientProxy try {
+ action(state, it)
+ } catch (ex: Exception) {
+ state.error(ex.message ?: "Failed to execute HTTP call")
+ }
+ })
+ }
}
\ No newline at end of file
diff --git a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/ConfigurationSteps.kt b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/ConfigurationSteps.kt
index 2b78ed3f1d..44d63a6137 100644
--- a/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/ConfigurationSteps.kt
+++ b/experimental/behave/src/scenario/kotlin/net/corda/behave/scenarios/steps/ConfigurationSteps.kt
@@ -39,6 +39,18 @@ class ConfigurationSteps : StepsBlock {
?: error("Unknown notary type '$notaryType'"))
}
+ Given("^a node (\\w+) of version ([^ ]+) with proxy$") { name, version ->
+ node(name).withRPCProxy(true)
+ .withDistribution(Distribution.fromVersionString(version))
+ }
+
+ Given("^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("^a (\\w+) notary (\\w+) of version ([^ ]+)$") { type, name, version ->
node(name)
.withDistribution(Distribution.fromVersionString(version))
@@ -70,7 +82,5 @@ class ConfigurationSteps : StepsBlock {
Given("^node (\\w+) has app installed: (.+)$") { name, app ->
node(name).withApp(app)
}
-
}
}
-
diff --git a/experimental/behave/src/scenario/resources/features/cash/currencies.feature b/experimental/behave/src/scenario/resources/features/cash/currencies.feature
index 2b2ae5d285..614f54a81c 100644
--- a/experimental/behave/src/scenario/resources/features/cash/currencies.feature
+++ b/experimental/behave/src/scenario/resources/features/cash/currencies.feature
@@ -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
@@ -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
@@ -32,4 +32,4 @@ Feature: Cash - Issuable Currencies
Examples:
| Node-Version |
- | master |
+ | r3-master |
diff --git a/experimental/behave/src/scenario/resources/features/database/connection.feature b/experimental/behave/src/scenario/resources/features/database/connection.feature
index 6bbdf73c09..ad96774114 100644
--- a/experimental/behave/src/scenario/resources/features/database/connection.feature
+++ b/experimental/behave/src/scenario/resources/features/database/connection.feature
@@ -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 |
\ No newline at end of file
+# To run this scenario using other DB providers you must ensure that Docker is running locally
+# | r3-master | postgreSQL |
+# | r3-master | SQL Server |
\ No newline at end of file
diff --git a/experimental/behave/src/scenario/resources/features/startup/logging.feature b/experimental/behave/src/scenario/resources/features/startup/logging.feature
index ce4ef490ed..0e0138becc 100644
--- a/experimental/behave/src/scenario/resources/features/startup/logging.feature
+++ b/experimental/behave/src/scenario/resources/features/startup/logging.feature
@@ -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
@@ -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
@@ -30,5 +30,19 @@ Feature: Startup Information - Logging
And node PartyA is on 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
+ And a node PartyB of version
+ And a node PartyC of version
+ And a nonvalidating notary Notary of 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 |
diff --git a/experimental/behave/src/scenario/resources/scripts/run-behave-features.sh b/experimental/behave/src/scenario/resources/scripts/run-behave-features.sh
index e016d00107..86220635d4 100755
--- a/experimental/behave/src/scenario/resources/scripts/run-behave-features.sh
+++ b/experimental/behave/src/scenario/resources/scripts/run-behave-features.sh
@@ -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
diff --git a/experimental/behave/src/smoke-test/kotlin/net/corda/behave/process/DBMigrationToolTests.kt b/experimental/behave/src/smoke-test/kotlin/net/corda/behave/process/DBMigrationToolTests.kt
new file mode 100644
index 0000000000..3d242ac613
--- /dev/null
+++ b/experimental/behave/src/smoke-test/kotlin/net/corda/behave/process/DBMigrationToolTests.kt
@@ -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= -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)
+ }
+}
\ No newline at end of file
diff --git a/experimental/behave/src/smoke-test/kotlin/net/corda/behave/process/DoormanCommandTests.kt b/experimental/behave/src/smoke-test/kotlin/net/corda/behave/process/DoormanCommandTests.kt
new file mode 100644
index 0000000000..9747f53416
--- /dev/null
+++ b/experimental/behave/src/smoke-test/kotlin/net/corda/behave/process/DoormanCommandTests.kt
@@ -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=
+ 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)
+ }
+}
\ No newline at end of file
diff --git a/experimental/behave/src/smoke-test/kotlin/net/corda/behave/process/RPCProxyCommandTests.kt b/experimental/behave/src/smoke-test/kotlin/net/corda/behave/process/RPCProxyCommandTests.kt
new file mode 100644
index 0000000000..c9d5358ec9
--- /dev/null
+++ b/experimental/behave/src/smoke-test/kotlin/net/corda/behave/process/RPCProxyCommandTests.kt
@@ -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()
+ }
+}
\ No newline at end of file
diff --git a/experimental/behave/src/smoke-test/kotlin/net/corda/behave/service/proxy/CordaRPCProxyClientTest.kt b/experimental/behave/src/smoke-test/kotlin/net/corda/behave/service/proxy/CordaRPCProxyClientTest.kt
new file mode 100644
index 0000000000..1d6f7caf36
--- /dev/null
+++ b/experimental/behave/src/smoke-test/kotlin/net/corda/behave/service/proxy/CordaRPCProxyClientTest.kt
@@ -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")
+ }
+}
\ No newline at end of file
diff --git a/experimental/behave/src/smoke-test/kotlin/net/corda/behave/service/proxy/OSNetworkTest.kt b/experimental/behave/src/smoke-test/kotlin/net/corda/behave/service/proxy/OSNetworkTest.kt
new file mode 100644
index 0000000000..54ddaab82f
--- /dev/null
+++ b/experimental/behave/src/smoke-test/kotlin/net/corda/behave/service/proxy/OSNetworkTest.kt
@@ -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()
+ }
+ }
+}
\ No newline at end of file
diff --git a/experimental/behave/src/smoke-test/kotlin/net/corda/behave/service/proxy/R3CordaNetworkTest.kt b/experimental/behave/src/smoke-test/kotlin/net/corda/behave/service/proxy/R3CordaNetworkTest.kt
new file mode 100644
index 0000000000..f314348ea2
--- /dev/null
+++ b/experimental/behave/src/smoke-test/kotlin/net/corda/behave/service/proxy/R3CordaNetworkTest.kt
@@ -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()
+ }
+ }
+}
\ No newline at end of file
diff --git a/experimental/behave/src/test/kotlin/net/corda/behave/network/NetworkTests.kt b/experimental/behave/src/test/kotlin/net/corda/behave/network/NetworkTests.kt
index 38497cc0f5..113c3489a7 100644
--- a/experimental/behave/src/test/kotlin/net/corda/behave/network/NetworkTests.kt
+++ b/experimental/behave/src/test/kotlin/net/corda/behave/network/NetworkTests.kt
@@ -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`() {
diff --git a/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt b/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt
index 55f565f817..88161f4f07 100644
--- a/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt
+++ b/experimental/behave/src/test/kotlin/net/corda/behave/process/CommandTests.kt
@@ -31,7 +31,7 @@ class CommandTests {
@Test
fun `output stream for command can be observed`() {
val subscriber = TestSubscriber()
- val exitCode = Command(listOf("ls", "/")).use(subscriber) { _, output ->
+ val exitCode = Command(listOf("ls", "/")).use(subscriber) { _, _ ->
subscriber.awaitTerminalEvent()
subscriber.assertCompleted()
subscriber.assertNoErrors()
diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionOracleImpl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionOracleImpl.kt
index 24d3bc9c86..bf9e6170b4 100644
--- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionOracleImpl.kt
+++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionOracleImpl.kt
@@ -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, lockId: UUID, notary: Party?,
onlyFromIssuerParties: Set, withIssuerRefs: Set, withResultSet: (ResultSet) -> Boolean): Boolean {
diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt
index d219fb3a2e..33053ddabf 100644
--- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt
+++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionPostgreSQLImpl.kt
@@ -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'"
diff --git a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionSQLServerImpl.kt b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionSQLServerImpl.kt
index bd9f6c16a7..20d0c5343b 100644
--- a/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionSQLServerImpl.kt
+++ b/finance/src/main/kotlin/net/corda/finance/contracts/asset/cash/selection/CashSelectionSQLServerImpl.kt
@@ -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, lockId: UUID, notary: Party?,
onlyFromIssuerParties: Set,
diff --git a/samples/simm-valuation-demo/build.gradle b/samples/simm-valuation-demo/build.gradle
index 661fe4976e..80de618169 100644
--- a/samples/simm-valuation-demo/build.gradle
+++ b/samples/simm-valuation-demo/build.gradle
@@ -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
}
}
}
diff --git a/samples/simm-valuation-demo/src/system-test/scenario/kotlin/net/corda/vega/scenarios/SIMMValuationStepsProvider.kt b/samples/simm-valuation-demo/src/system-test/scenario/kotlin/net/corda/vega/scenarios/SIMMValuationStepsProvider.kt
new file mode 100644
index 0000000000..6448d9aeb1
--- /dev/null
+++ b/samples/simm-valuation-demo/src/system-test/scenario/kotlin/net/corda/vega/scenarios/SIMMValuationStepsProvider.kt
@@ -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()
+}
diff --git a/samples/simm-valuation-demo/src/system-test/scenario/kotlin/net/corda/vega/scenarios/helpers/SIMMValuation.kt b/samples/simm-valuation-demo/src/system-test/scenario/kotlin/net/corda/vega/scenarios/helpers/SIMMValuation.kt
new file mode 100644
index 0000000000..15f580da5e
--- /dev/null
+++ b/samples/simm-valuation-demo/src/system-test/scenario/kotlin/net/corda/vega/scenarios/helpers/SIMMValuation.kt
@@ -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")
+ }
+}
\ No newline at end of file
diff --git a/samples/simm-valuation-demo/src/system-test/scenario/kotlin/net/corda/vega/scenarios/steps/SimmValuationSteps.kt b/samples/simm-valuation-demo/src/system-test/scenario/kotlin/net/corda/vega/scenarios/steps/SimmValuationSteps.kt
new file mode 100644
index 0000000000..424c044e82
--- /dev/null
+++ b/samples/simm-valuation-demo/src/system-test/scenario/kotlin/net/corda/vega/scenarios/steps/SimmValuationSteps.kt
@@ -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("^node (\\w+) can trade with node (\\w+)$") { nodeA, nodeB ->
+ state.withNetwork {
+ simmValuation.trade(nodeA, nodeB)
+ }
+ }
+
+ Then("^node (\\w+) can run portfolio valuation$") { node ->
+ state.withNetwork {
+ simmValuation.runValuation(node)
+ }
+ }
+
+ Then("^node (\\w+) portfolio valuation is (\\d+)$") { _, value ->
+ state.withNetwork {
+ simmValuation.checkValuation(value)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/simm-valuation-demo/src/system-test/scenario/resources/features/simm-valuation.feature b/samples/simm-valuation-demo/src/system-test/scenario/resources/features/simm-valuation.feature
new file mode 100644
index 0000000000..0bc22ea8e3
--- /dev/null
+++ b/samples/simm-valuation-demo/src/system-test/scenario/resources/features/simm-valuation.feature
@@ -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 with proxy
+ And node PartyA has app installed:
+ And a node PartyB of version
+ And node PartyB has app installed:
+ And a nonvalidating notary Notary of version
+ When the network is ready
+ And node PartyA has loaded app
+ And node PartyB has loaded app
+ 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
+ And node PartyB portfolio valuation is
+
+ 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 with proxy
+ And node PartyA has app installed:
+ And a node PartyB of version
+ And node PartyB has app installed:
+ And a nonvalidating notary Notary of version
+ When the network is ready
+ And node PartyA has loaded app
+ And node PartyB has loaded app
+ 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
+ And node PartyB portfolio valuation is
+
+ 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 with proxy
+ And node PartyA has app installed:
+ And a node PartyB of version
+ And node PartyB has app installed:
+ And a nonvalidating notary Notary of version
+ When the network is ready
+ And node PartyA has loaded app
+ And node PartyB has loaded app
+ 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
+ And node PartyB portfolio valuation is
+
+ 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 |
diff --git a/samples/simm-valuation-demo/src/system-test/scenario/resources/scripts/run-behave-simm-valuation.sh b/samples/simm-valuation-demo/src/system-test/scenario/resources/scripts/run-behave-simm-valuation.sh
new file mode 100755
index 0000000000..6663b57517
--- /dev/null
+++ b/samples/simm-valuation-demo/src/system-test/scenario/resources/scripts/run-behave-simm-valuation.sh
@@ -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
\ No newline at end of file
diff --git a/testing/qa/behave/compatibility/resources/scripts/run-interoperability.sh b/testing/qa/behave/compatibility/resources/scripts/run-interoperability.sh
index 2ea4d4490f..1ce4643ad1 100755
--- a/testing/qa/behave/compatibility/resources/scripts/run-interoperability.sh
+++ b/testing/qa/behave/compatibility/resources/scripts/run-interoperability.sh
@@ -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
diff --git a/testing/qa/behave/functional/resources/scripts/run-functional.sh b/testing/qa/behave/functional/resources/scripts/run-functional.sh
index a0c2c45a4a..4853c28a43 100755
--- a/testing/qa/behave/functional/resources/scripts/run-functional.sh
+++ b/testing/qa/behave/functional/resources/scripts/run-functional.sh
@@ -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