From 0788e8d64c6cbaed57d6b61e958c6d5099c38bf9 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Wed, 1 Jun 2016 13:31:48 +0100 Subject: [PATCH 01/48] IRS demo to now has roles for NodeA/NodeB and most options are now~ optional. NodeA can be run without the shell script. --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index e1d270b6bb..7fef5004d5 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -249,6 +249,7 @@ private fun runNode(nodeParams : NodeParams) : Unit { } catch(e: InterruptedException) { node.stop() } + exitProcess(0) } private fun runUploadRates() { From 963976806946ac6c3e8b7f8d4cd6adcb1991581f Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Mon, 6 Jun 2016 10:21:27 +0100 Subject: [PATCH 02/48] Corrected the currency mismatch in the example trade. --- src/main/resources/com/r3corda/demos/example-irs-trade.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/com/r3corda/demos/example-irs-trade.json b/src/main/resources/com/r3corda/demos/example-irs-trade.json index 19cc8e8951..5c602e3bbd 100644 --- a/src/main/resources/com/r3corda/demos/example-irs-trade.json +++ b/src/main/resources/com/r3corda/demos/example-irs-trade.json @@ -96,7 +96,7 @@ "addressForTransfers": "", "exposure": {}, "localBusinessDay": [ "London" , "NewYork" ], - "dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360", + "dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360", "tradeID": "tradeXXX", "hashLegalDocs": "put hash here" } From 507d9ea4ae4b224b946d7898a81a604ea3929ce1 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Fri, 3 Jun 2016 16:36:39 +0100 Subject: [PATCH 03/48] Added new integration test for the IRSDemo and refactored the demo to run in integration tests. --- .../kotlin/com/r3corda/node/internal/Node.kt | 2 +- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 85 ++++++++++++++++--- .../com/r3corda/core/testing/IRSDemoTest.kt | 66 ++++++++++++++ 3 files changed, 138 insertions(+), 15 deletions(-) create mode 100644 src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt diff --git a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt index 6c3ddbdda4..f78fa99c71 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt @@ -52,7 +52,7 @@ class ConfigurationException(message: String) : Exception(message) * Listed clientAPI classes are assumed to have to take a single APIServer constructor parameter * @param clock The clock used within the node and by all protocols etc */ -class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration, +open class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration, networkMapAddress: NodeInfo?, advertisedServices: Set, clock: Clock = NodeClock(), val clientAPIs: List> = listOf()) : AbstractNode(dir, configuration, networkMapAddress, advertisedServices, clock) { diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 7fef5004d5..49300dbb4a 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -4,6 +4,7 @@ import com.google.common.net.HostAndPort import com.typesafe.config.ConfigFactory import com.r3corda.core.crypto.Party import com.r3corda.core.logElapsedTime +import com.r3corda.core.messaging.MessagingService import com.r3corda.node.internal.Node import com.r3corda.node.services.config.NodeConfiguration import com.r3corda.node.services.config.NodeConfigurationFromConfig @@ -26,6 +27,7 @@ import com.r3corda.node.services.transactions.SimpleNotaryService import joptsimple.OptionParser import joptsimple.OptionSet import joptsimple.OptionSpec +import joptsimple.OptionSpecBuilder import java.io.DataOutputStream import java.io.File import java.net.HttpURLConnection @@ -33,6 +35,7 @@ import java.net.URL import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths +import java.time.Clock import java.util.* import kotlin.concurrent.fixedRateTimer import kotlin.system.exitProcess @@ -83,7 +86,28 @@ private class NotSetupException: Throwable { constructor(message: String): super(message) {} } +val messageNetwork = InMemoryMessagingNetwork() + +class DemoNode(messagingService: MessagingService, dir: Path, p2pAddr: HostAndPort, config: NodeConfiguration, + networkMapAddress: NodeInfo?, advertisedServices: Set, + clock: Clock, clientAPIs: List> = listOf()) + : Node(dir, p2pAddr, config, networkMapAddress, advertisedServices, clock, clientAPIs) { + + val messagingService = messagingService + override fun makeMessagingService(): MessagingService { + return messagingService + } + + override fun startMessagingService() { + + } +} + fun main(args: Array) { + exitProcess(runIRSDemo(args)) +} + +fun runIRSDemo(args: Array, useInMemoryMessaging: Boolean = false): Int { val parser = OptionParser() val demoArgs = setupArgs(parser) val options = try { @@ -91,7 +115,7 @@ fun main(args: Array) { } catch (e: Exception) { println(e.message) printHelp() - exitProcess(1) + return 1 } // Suppress the Artemis MQ noise, and activate the demo logging. @@ -114,14 +138,12 @@ fun main(args: Array) { "http://localhost:" + (Node.DEFAULT_PORT + 1) } - if (runTrade(tradeId, host)) { - exitProcess(0) - } else { - exitProcess(1) + if (!runTrade(tradeId, host)) { + return 1 } } else { println("Please provide a trade ID") - exitProcess(1) + return 1 } } else if(role == IRSDemoRole.Date) { val dateStrArgs = options.valuesOf(demoArgs.nonOptions) @@ -133,10 +155,12 @@ fun main(args: Array) { "http://localhost:" + (Node.DEFAULT_PORT + 1) } - runDateChange(dateStr, host) + if(!runDateChange(dateStr)) { + return 1 + } } else { println("Please provide a date") - exitProcess(1) + return 1 } } else { // If these directory and identity file arguments aren't specified then we can assume a default setup and @@ -147,14 +171,14 @@ fun main(args: Array) { } try { - runNode(configureNodeParams(role, demoArgs, options)) + runNode(configureNodeParams(role, demoArgs, options), useInMemoryMessaging) } catch (e: NotSetupException) { println(e.message) - exitProcess(1) + return 1 } - - exitProcess(0) } + + return 0 } private fun setupArgs(parser: OptionParser): DemoArgs { @@ -233,8 +257,11 @@ private fun configureNodeParams(role: IRSDemoRole, args: DemoArgs, options: Opti return nodeParams } -private fun runNode(nodeParams : NodeParams) : Unit { - val node = startNode(nodeParams) +private fun runNode(nodeParams : NodeParams, useInMemoryMessaging: Boolean) : Unit { + val node = when(useInMemoryMessaging) { + true -> startDemoNode(nodeParams) + false -> startNode(nodeParams) + } // Register handlers for the demo AutoOfferProtocol.Handler.register(node) UpdateBusinessDayProtocol.Handler.register(node) @@ -417,6 +444,36 @@ private fun startNode(params : NodeParams) : Node { return node } +private fun startDemoNode(params : NodeParams) : Node { + val config = createNodeConfig(params) + val advertisedServices: Set + val myNetAddr = HostAndPort.fromString(params.address).withDefaultPort(Node.DEFAULT_PORT) + val networkMapId = if (params.mapAddress.equals(params.address)) { + // This node provides network map and notary services + advertisedServices = setOf(NetworkMapService.Type, NotaryService.Type) + null + } else { + advertisedServices = setOf(NodeInterestRates.Type) + + val handle = InMemoryMessagingNetwork.Handle(createNodeAParams().id, params.defaultLegalName) + nodeInfo(handle, params.identityFile, setOf(NetworkMapService.Type, NotaryService.Type)) + } + + val messageService = messageNetwork.createNodeWithID(false, params.id).start().get() + val node = logElapsedTime("Node startup") { DemoNode(messageService, params.dir, myNetAddr, config, networkMapId, + advertisedServices, DemoClock(), + listOf(InterestRateSwapAPI::class.java)).setup().start() } + + // TODO: This should all be replaced by the identity service being updated + // as the network map changes. + val identityFile = params.tradeWithIdentities[0] + val handle = InMemoryMessagingNetwork.Handle(1 - params.id, "Other Node") + val peerId = nodeInfo(handle, identityFile) + node.services.identityService.registerIdentity(peerId.identity) + + return node +} + private fun getRoleDir(role: IRSDemoRole) : Path { when(role) { IRSDemoRole.NodeA -> return Paths.get("nodeA") diff --git a/src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt new file mode 100644 index 0000000000..d922be68fe --- /dev/null +++ b/src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -0,0 +1,66 @@ +package com.r3corda.core.testing + +import com.r3corda.demos.runIRSDemo +import kotlin.concurrent.thread +import kotlin.test.assertEquals +import org.junit.Test +import java.nio.file.Path +import java.nio.file.Paths + +class IRSDemoTest { + @Test fun `runs IRS demo`() { + val dirA = Paths.get("./nodeA") + val dirB = Paths.get("./nodeB") + try { + setupNodeA(dirA) + setupNodeB(dirB) + startNodeA(dirA) + startNodeB(dirB) + runTrade() + runDateChange() + stopNodeA() + stopNodeB() + } finally { + cleanup(dirA) + cleanup(dirB) + } + } +} + +private fun setupNodeA(dir: Path) { + runIRSDemo(arrayOf("--role", "SetupNodeA", "--dir", dir.toString())) +} + +private fun setupNodeB(dir: Path) { + runIRSDemo(arrayOf("--role", "SetupNodeB", "--dir", dir.toString())) +} +private fun startNodeA(dir: Path) { + thread(true, false, null, "NodeA", -1, { runIRSDemo(arrayOf("--role", "NodeA", "--dir", dir.toString()), true) }) + Thread.sleep(15000) +} + +private fun startNodeB(dir: Path) { + thread(true, false, null, "NodeB", -1, { runIRSDemo(arrayOf("--role", "NodeB", "--dir", dir.toString()), true) }) + Thread.sleep(15000) +} + +private fun stopNodeA() { + +} + +private fun stopNodeB() { + +} + +private fun runTrade() { + assertEquals(runIRSDemo(arrayOf("--role", "Trade", "trade1")), 0) +} + +private fun runDateChange() { + assertEquals(runIRSDemo(arrayOf("--role", "Date", "2017-01-02")), 0) +} + +private fun cleanup(dir: Path) { + println("Erasing: " + dir.toString()) + dir.toFile().deleteRecursively() +} \ No newline at end of file From 90a24588bc6c236950e25c7adf5b597b8feccf87 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Mon, 6 Jun 2016 09:20:04 +0100 Subject: [PATCH 04/48] Added TODOs and replaced default path with an exception. --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 49300dbb4a..10df92ac2f 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -98,9 +98,7 @@ class DemoNode(messagingService: MessagingService, dir: Path, p2pAddr: HostAndPo return messagingService } - override fun startMessagingService() { - - } + override fun startMessagingService() = Unit } fun main(args: Array) { @@ -276,7 +274,6 @@ private fun runNode(nodeParams : NodeParams, useInMemoryMessaging: Boolean) : Un } catch(e: InterruptedException) { node.stop() } - exitProcess(0) } private fun runUploadRates() { @@ -467,6 +464,8 @@ private fun startDemoNode(params : NodeParams) : Node { // TODO: This should all be replaced by the identity service being updated // as the network map changes. val identityFile = params.tradeWithIdentities[0] + // Since in integration tests there are only two nodes with IDs 0 and 1, this hack will work + // TODO: Get Artemis working with two nodes in the same process or come up with a better solution val handle = InMemoryMessagingNetwork.Handle(1 - params.id, "Other Node") val peerId = nodeInfo(handle, identityFile) node.services.identityService.registerIdentity(peerId.identity) From 9fc89fc4a20850ebd013766343ec2a9667376acf Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Mon, 6 Jun 2016 10:10:25 +0100 Subject: [PATCH 05/48] Connections now timeout correctly if something goes wrong with the server. --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 10df92ac2f..36180c2d90 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -200,6 +200,7 @@ private fun setup(params: NodeParams) { } private fun runDateChange(date: String, host: String) : Boolean { + println("Changing date to " + date) val url = URL(host + "/api/irs/demodate") if(putJson(url, "\"" + date + "\"")) { println("Date changed") From 5de855e045aed65484b049910f6aade556e6bc90 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Mon, 6 Jun 2016 15:55:15 +0100 Subject: [PATCH 06/48] Fixed a merge error and parameterised host for upload rates. --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 8 ++++---- src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt | 1 + 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 36180c2d90..430b4dc611 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -153,7 +153,7 @@ fun runIRSDemo(args: Array, useInMemoryMessaging: Boolean = false): Int "http://localhost:" + (Node.DEFAULT_PORT + 1) } - if(!runDateChange(dateStr)) { + if(!runDateChange(dateStr, host)) { return 1 } } else { @@ -267,7 +267,7 @@ private fun runNode(nodeParams : NodeParams, useInMemoryMessaging: Boolean) : Un ExitServerProtocol.Handler.register(node) if(nodeParams.uploadRates) { - runUploadRates() + runUploadRates("http://localhost:31341") } try { @@ -277,12 +277,12 @@ private fun runNode(nodeParams : NodeParams, useInMemoryMessaging: Boolean) : Un } } -private fun runUploadRates() { +private fun runUploadRates(host) { val fileContents = IOUtils.toString(NodeParams::class.java.getResource("example.rates.txt")) var timer : Timer? = null timer = fixedRateTimer("upload-rates", false, 0, 5000, { try { - val url = URL("http://localhost:31341/upload/interest-rates") + val url = URL(host + "/upload/interest-rates") if(uploadFile(url, fileContents)) { timer!!.cancel() println("Rates uploaded successfully") diff --git a/src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index d922be68fe..5534ad7bb0 100644 --- a/src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -34,6 +34,7 @@ private fun setupNodeA(dir: Path) { private fun setupNodeB(dir: Path) { runIRSDemo(arrayOf("--role", "SetupNodeB", "--dir", dir.toString())) } + private fun startNodeA(dir: Path) { thread(true, false, null, "NodeA", -1, { runIRSDemo(arrayOf("--role", "NodeA", "--dir", dir.toString()), true) }) Thread.sleep(15000) From b050411810bf008db6a3e6eb9493d3754535cd64 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Mon, 6 Jun 2016 16:13:34 +0100 Subject: [PATCH 07/48] Fixed compile error in IRSDemo and updated demo data to match new format. --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 430b4dc611..10dde98431 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -277,7 +277,7 @@ private fun runNode(nodeParams : NodeParams, useInMemoryMessaging: Boolean) : Un } } -private fun runUploadRates(host) { +private fun runUploadRates(host: String) { val fileContents = IOUtils.toString(NodeParams::class.java.getResource("example.rates.txt")) var timer : Timer? = null timer = fixedRateTimer("upload-rates", false, 0, 5000, { From f6069e1e1533d4e1b7585282e60268152486de00 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Tue, 7 Jun 2016 10:26:51 +0100 Subject: [PATCH 08/48] Error now occurs on upload if no files are sent. Added apache httpcomponents as a dependency. --- node/build.gradle | 2 ++ .../kotlin/com/r3corda/node/servlets/DataUploadServlet.kt | 7 +++++++ 2 files changed, 9 insertions(+) diff --git a/node/build.gradle b/node/build.gradle index f9005d9349..9716079360 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -90,6 +90,8 @@ dependencies { // TypeSafe Config: for simple and human friendly config files. compile "com.typesafe:config:1.3.0" + compile "org.apache.httpcomponents:httpclient:4.5.2" + // Unit testing helpers. testCompile 'junit:junit:4.12' testCompile 'org.assertj:assertj-core:3.4.1' diff --git a/node/src/main/kotlin/com/r3corda/node/servlets/DataUploadServlet.kt b/node/src/main/kotlin/com/r3corda/node/servlets/DataUploadServlet.kt index e8551d2b98..6a0944dd16 100644 --- a/node/src/main/kotlin/com/r3corda/node/servlets/DataUploadServlet.kt +++ b/node/src/main/kotlin/com/r3corda/node/servlets/DataUploadServlet.kt @@ -35,6 +35,13 @@ class DataUploadServlet : HttpServlet() { val upload = ServletFileUpload() val iterator = upload.getItemIterator(req) val messages = ArrayList() + + if(!iterator.hasNext()) + { + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Got an upload request with no files") + return + } + while (iterator.hasNext()) { val item = iterator.next() if (item.name != null && !acceptor.acceptableFileExtensions.any { item.name.endsWith(it) }) { From 60daf8059fa798b8ae38422c68cc1c4574b1c169 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Tue, 7 Jun 2016 14:30:58 +0100 Subject: [PATCH 09/48] Removed dependency added in previous commit. Fixed upload code on IRS Demo --- node/build.gradle | 2 -- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 6 +++++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/node/build.gradle b/node/build.gradle index 9716079360..f9005d9349 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -90,8 +90,6 @@ dependencies { // TypeSafe Config: for simple and human friendly config files. compile "com.typesafe:config:1.3.0" - compile "org.apache.httpcomponents:httpclient:4.5.2" - // Unit testing helpers. testCompile 'junit:junit:4.12' testCompile 'org.assertj:assertj-core:3.4.1' diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 10dde98431..a8b9ec9417 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -40,7 +40,11 @@ import java.util.* import kotlin.concurrent.fixedRateTimer import kotlin.system.exitProcess import org.apache.commons.io.IOUtils +import org.glassfish.jersey.client.JerseyClientBuilder +import org.glassfish.jersey.client.JerseyWebTarget import java.io.FileNotFoundException +import javax.ws.rs.client.Entity +import javax.ws.rs.client.Invocation // IRS DEMO // @@ -311,7 +315,7 @@ private fun sendJson(url: URL, data: String, method: String) : Boolean { outStream.writeBytes(data) outStream.close() - return when(connection.responseCode) { + return when (connection.responseCode) { 200 -> true 201 -> true else -> { From de27b1e8dec7ecd9e48d98af322e10e394f3f5d2 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Tue, 7 Jun 2016 17:17:19 +0100 Subject: [PATCH 10/48] Improved error handling in IRS demo --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 32 +++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index a8b9ec9417..d598c010a4 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -40,11 +40,7 @@ import java.util.* import kotlin.concurrent.fixedRateTimer import kotlin.system.exitProcess import org.apache.commons.io.IOUtils -import org.glassfish.jersey.client.JerseyClientBuilder -import org.glassfish.jersey.client.JerseyWebTarget -import java.io.FileNotFoundException -import javax.ws.rs.client.Entity -import javax.ws.rs.client.Invocation +import java.net.SocketTimeoutException // IRS DEMO // @@ -306,22 +302,28 @@ private fun sendJson(url: URL, data: String, method: String) : Boolean { connection.useCaches = false connection.requestMethod = method connection.connectTimeout = 5000 - connection.readTimeout = 5000 + connection.readTimeout = 10000 connection.setRequestProperty("Connection", "Keep-Alive") connection.setRequestProperty("Cache-Control", "no-cache") connection.setRequestProperty("Content-Type", "application/json") connection.setRequestProperty("Content-Length", data.length.toString()) - val outStream = DataOutputStream(connection.outputStream) - outStream.writeBytes(data) - outStream.close() - return when (connection.responseCode) { - 200 -> true - 201 -> true - else -> { - println("Failed to " + method + " data. Status Code: " + connection.responseCode + ". Mesage: " + connection.responseMessage) - false + try { + val outStream = DataOutputStream(connection.outputStream) + outStream.writeBytes(data) + outStream.close() + + return when (connection.responseCode) { + 200 -> true + 201 -> true + else -> { + println("Failed to " + method + " data. Status Code: " + connection.responseCode + ". Mesage: " + connection.responseMessage) + false + } } + } catch(e: SocketTimeoutException) { + println("Server took too long to respond") + return false } } From b61b36289125105ac6c606c0459c34c17dc113c3 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Tue, 7 Jun 2016 18:25:11 +0100 Subject: [PATCH 11/48] Setup TraderDemo test. Moved DemoNode to a common file. Modified TraderDemo to be tested. --- src/main/kotlin/com/r3corda/demos/DemoNode.kt | 27 +++++++++++++ src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 15 ------- .../kotlin/com/r3corda/demos/TraderDemo.kt | 39 ++++++++++++------- .../com/r3corda/core/testing/TradeDemoTest.kt | 34 ++++++++++++++++ 4 files changed, 87 insertions(+), 28 deletions(-) create mode 100644 src/main/kotlin/com/r3corda/demos/DemoNode.kt create mode 100644 src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt diff --git a/src/main/kotlin/com/r3corda/demos/DemoNode.kt b/src/main/kotlin/com/r3corda/demos/DemoNode.kt new file mode 100644 index 0000000000..6f04fbabea --- /dev/null +++ b/src/main/kotlin/com/r3corda/demos/DemoNode.kt @@ -0,0 +1,27 @@ +package com.r3corda.demos + +import com.google.common.net.HostAndPort +import com.r3corda.core.messaging.MessagingService +import com.r3corda.core.node.NodeInfo +import com.r3corda.core.node.services.ServiceType +import com.r3corda.node.internal.Node +import com.r3corda.node.serialization.NodeClock +import com.r3corda.node.services.config.NodeConfiguration +import com.r3corda.node.services.network.InMemoryMessagingNetwork +import java.nio.file.Path +import java.time.Clock + +val messageNetwork = InMemoryMessagingNetwork() + +class DemoNode(messagingService: MessagingService, dir: Path, p2pAddr: HostAndPort, config: NodeConfiguration, + networkMapAddress: NodeInfo?, advertisedServices: Set, + clock: Clock = NodeClock(), clientAPIs: List> = listOf()) +: Node(dir, p2pAddr, config, networkMapAddress, advertisedServices, clock, clientAPIs) { + + val messagingService = messagingService + override fun makeMessagingService(): MessagingService { + return messagingService + } + + override fun startMessagingService() = Unit +} \ No newline at end of file diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index d598c010a4..f06f54097d 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -86,21 +86,6 @@ private class NotSetupException: Throwable { constructor(message: String): super(message) {} } -val messageNetwork = InMemoryMessagingNetwork() - -class DemoNode(messagingService: MessagingService, dir: Path, p2pAddr: HostAndPort, config: NodeConfiguration, - networkMapAddress: NodeInfo?, advertisedServices: Set, - clock: Clock, clientAPIs: List> = listOf()) - : Node(dir, p2pAddr, config, networkMapAddress, advertisedServices, clock, clientAPIs) { - - val messagingService = messagingService - override fun makeMessagingService(): MessagingService { - return messagingService - } - - override fun startMessagingService() = Unit -} - fun main(args: Array) { exitProcess(runIRSDemo(args)) } diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index 90d7917d7d..e89ae844df 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -68,13 +68,27 @@ enum class Role { val DIRNAME = "trader-demo" fun main(args: Array) { + exitProcess(runTraderDemo(args)) +} + +fun runTraderDemo(args: Array, useInMemoryMessaging: Boolean = false): Int { + val cashIssuerKey = generateKeyPair() + val cashIssuer = Party("Trusted cash issuer", cashIssuerKey.public) + val amount = 1000.DOLLARS `issued by` cashIssuer.ref(1) val parser = OptionParser() val roleArg = parser.accepts("role").withRequiredArg().ofType(Role::class.java).required() val myNetworkAddress = parser.accepts("network-address").withRequiredArg().defaultsTo("localhost") val theirNetworkAddress = parser.accepts("other-network-address").withRequiredArg().defaultsTo("localhost") - val options = parseOptions(args, parser) + val options = try { + parser.parse(*args) + } catch (e: Exception) { + println(e.message) + println("Please refer to the documentation in docs/build/index.html to learn how to run the demo.") + return 1 + } + val role = options.valueOf(roleArg)!! val myNetAddr = HostAndPort.fromString(options.valueOf(myNetworkAddress)).withDefaultPort( @@ -130,6 +144,11 @@ fun main(args: Array) { NodeInfo(ArtemisMessagingService.makeRecipient(theirNetAddr), party, setOf(NetworkMapService.Type)) } + val id = when(role) { + Role.BUYER -> 0 + Role.SELLER -> 1 + } + val messageService = messageNetwork.createNodeWithID(false, id).start().get() // And now construct then start the node object. It takes a little while. val node = logElapsedTime("Node startup") { Node(directory, myNetAddr, config, networkMapId, advertisedServices).setup().start() @@ -148,19 +167,13 @@ fun main(args: Array) { } else { runSeller(myNetAddr, node, theirNetAddr, amount) } + + return 0 } -fun parseOptions(args: Array, parser: OptionParser): OptionSet { - try { - return parser.parse(*args) - } catch (e: Exception) { - println(e.message) - println("Please refer to the documentation in docs/build/index.html to learn how to run the demo.") - exitProcess(1) - } } -fun runSeller(myNetAddr: HostAndPort, node: Node, theirNetAddr: HostAndPort, amount: Amount>) { +private fun runSeller(myNetAddr: HostAndPort, node: Node, theirNetAddr: HostAndPort) { // The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash. // // The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an @@ -187,7 +200,7 @@ fun runSeller(myNetAddr: HostAndPort, node: Node, theirNetAddr: HostAndPort, amo node.stop() } -fun runBuyer(node: Node, amount: Amount>) { +private fun runBuyer(node: Node, amount: Amount>) { // Buyer will fetch the attachment from the seller automatically when it resolves the transaction. // For demo purposes just extract attachment jars when saved to disk, so the user can explore them. val attachmentsPath = (node.storage.attachments as NodeAttachmentService).let { @@ -211,7 +224,7 @@ fun runBuyer(node: Node, amount: Amount>) { val DEMO_TOPIC = "initiate.demo.trade" -class TraderDemoProtocolBuyer(private val attachmentsPath: Path, +private class TraderDemoProtocolBuyer(private val attachmentsPath: Path, val notary: Party, val amount: Amount>) : ProtocolLogic() { companion object { @@ -289,7 +302,7 @@ ${Emoji.renderIfSupported(cpIssuance)}""") } } -class TraderDemoProtocolSeller(val myAddress: HostAndPort, +private class TraderDemoProtocolSeller(val myAddress: HostAndPort, val otherSide: SingleMessageRecipient, val amount: Amount>, override val progressTracker: ProgressTracker = TraderDemoProtocolSeller.tracker()) : ProtocolLogic() { diff --git a/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt b/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt new file mode 100644 index 0000000000..489cf5b74a --- /dev/null +++ b/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt @@ -0,0 +1,34 @@ +package com.r3corda.core.testing + +import com.r3corda.demos.runTraderDemo +import org.junit.Test +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.concurrent.thread +import kotlin.test.assertEquals + +class TraderDemoTest { + @Test fun `runs trader demo`() { + try { + runBuyer() + runSeller() + } finally { + cleanup() + } + } +} + +private fun runBuyer() { + thread(true, false, null, "Buyer", -1, { runTraderDemo(arrayOf("--role", "BUYER"), true) }) + Thread.sleep(5000) +} + +private fun runSeller() { + assertEquals(runTraderDemo(arrayOf("--role", "SELLER"), true), 0) +} + +private fun cleanup() { + val dir = Paths.get("trader-demo") + println("Erasing " + dir) + dir.toFile().deleteRecursively() +} \ No newline at end of file From 89b8b164f7df42356f933d0dcb6723aecc099cef Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Tue, 7 Jun 2016 18:57:48 +0100 Subject: [PATCH 12/48] Trader Demo now has in memory nodes working. --- .../kotlin/com/r3corda/demos/TraderDemo.kt | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index e89ae844df..9a53abbaba 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -24,6 +24,7 @@ import com.r3corda.core.utilities.ProgressTracker import com.r3corda.node.internal.Node import com.r3corda.node.services.config.NodeConfigurationFromConfig import com.r3corda.node.services.messaging.ArtemisMessagingService +import com.r3corda.node.services.network.InMemoryMessagingNetwork import com.r3corda.node.services.network.NetworkMapService import com.r3corda.node.services.persistence.NodeAttachmentService import com.r3corda.node.services.transactions.SimpleNotaryService @@ -122,6 +123,19 @@ fun runTraderDemo(args: Array, useInMemoryMessaging: Boolean = false): I NodeConfigurationFromConfig(override.withFallback(ConfigFactory.load())) } + val peerId: Int; + val id: Int + when (role) { + Role.BUYER -> { + peerId = 1 + id = 0 + } + Role.SELLER -> { + peerId = 0 + id = 1 + } + } + // Which services will this instance of the node provide to the network? val advertisedServices: Set @@ -142,11 +156,13 @@ fun runTraderDemo(args: Array, useInMemoryMessaging: Boolean = false): I advertisedServices = emptySet() cashIssuer = party NodeInfo(ArtemisMessagingService.makeRecipient(theirNetAddr), party, setOf(NetworkMapService.Type)) - } - val id = when(role) { - Role.BUYER -> 0 - Role.SELLER -> 1 + if(useInMemoryMessaging) { + val handle = InMemoryMessagingNetwork.Handle(peerId, "Other Node") + NodeInfo(handle, party, setOf(NetworkMapService.Type)) + } else { + NodeInfo(ArtemisMessagingService.makeRecipient(theirNetAddr), party, setOf(NetworkMapService.Type)) + } } val messageService = messageNetwork.createNodeWithID(false, id).start().get() // And now construct then start the node object. It takes a little while. @@ -171,8 +187,6 @@ fun runTraderDemo(args: Array, useInMemoryMessaging: Boolean = false): I return 0 } -} - private fun runSeller(myNetAddr: HostAndPort, node: Node, theirNetAddr: HostAndPort) { // The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash. // From 929b752b428f48bc49e6149897cc1aa874510b09 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Wed, 8 Jun 2016 11:58:46 +0100 Subject: [PATCH 13/48] Trader demo now works as a test using in memory messaging. --- .../kotlin/com/r3corda/demos/TraderDemo.kt | 66 ++++++++++++------- .../com/r3corda/core/testing/TradeDemoTest.kt | 3 +- 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index 9a53abbaba..918b49a72b 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -33,6 +33,7 @@ import com.r3corda.protocols.TwoPartyTradeProtocol import com.typesafe.config.ConfigFactory import joptsimple.OptionParser import joptsimple.OptionSet +import java.io.Serializable import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -64,6 +65,11 @@ enum class Role { SELLER } +private class Destination constructor(addr: Any, inMemory: Boolean): Serializable { + val inMemory = inMemory + val addr = addr +} + // And this is the directory under the current working directory where each node will create its own server directory, // which holds things like checkpoints, keys, databases, message logs etc. val DIRNAME = "trader-demo" @@ -105,6 +111,19 @@ fun runTraderDemo(args: Array, useInMemoryMessaging: Boolean = false): I } ) + val peerId: Int; + val id: Int + when (role) { + Role.BUYER -> { + peerId = 1 + id = 0 + } + Role.SELLER -> { + peerId = 0 + id = 1 + } + } + // Suppress the Artemis MQ noise, and activate the demo logging. // // The first two strings correspond to the first argument to StateMachineManager.add() but the way we handle logging @@ -123,19 +142,6 @@ fun runTraderDemo(args: Array, useInMemoryMessaging: Boolean = false): I NodeConfigurationFromConfig(override.withFallback(ConfigFactory.load())) } - val peerId: Int; - val id: Int - when (role) { - Role.BUYER -> { - peerId = 1 - id = 0 - } - Role.SELLER -> { - peerId = 0 - id = 1 - } - } - // Which services will this instance of the node provide to the network? val advertisedServices: Set @@ -181,13 +187,24 @@ fun runTraderDemo(args: Array, useInMemoryMessaging: Boolean = false): I if (role == Role.BUYER) { runBuyer(node, amount) } else { - runSeller(myNetAddr, node, theirNetAddr, amount) + val dest: Destination + val recipient: SingleMessageRecipient + + if(useInMemoryMessaging) { + recipient = InMemoryMessagingNetwork.Handle(peerId, "Other Node") + dest = Destination(InMemoryMessagingNetwork.Handle(id, role.toString()), true) + } else { + recipient = ArtemisMessagingService.makeRecipient(theirNetAddr) + dest = Destination(myNetAddr, false) + } + + runSeller(dest, node, recipient, amount)) } return 0 } -private fun runSeller(myNetAddr: HostAndPort, node: Node, theirNetAddr: HostAndPort) { +private fun runSeller(myAddr: Destination, node: Node, recipient: SingleMessageRecipient) { // The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash. // // The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an @@ -206,8 +223,7 @@ private fun runSeller(myNetAddr: HostAndPort, node: Node, theirNetAddr: HostAndP it.second.get() } } else { - val otherSide = ArtemisMessagingService.makeRecipient(theirNetAddr) - val seller = TraderDemoProtocolSeller(myNetAddr, otherSide, amount) + val seller = TraderDemoProtocolSeller(myAddr, recipient, amount) node.smm.add("demo.seller", seller).get() } @@ -264,16 +280,20 @@ private class TraderDemoProtocolBuyer(private val attachmentsPath: Path, // As the seller initiates the two-party trade protocol, here, we will be the buyer. try { progressTracker.currentStep = WAITING_FOR_SELLER_TO_CONNECT - val hostname = receive(DEMO_TOPIC, 0).validate { it.withDefaultPort(Node.DEFAULT_PORT) } - val newPartnerAddr = ArtemisMessagingService.makeRecipient(hostname) + val origin: Destination = receive(DEMO_TOPIC, 0).validate { it } + val recipient: SingleMessageRecipient = if(origin.inMemory) { + origin.addr as InMemoryMessagingNetwork.Handle + } else { + ArtemisMessagingService.makeRecipient(origin.addr as HostAndPort) + } // The session ID disambiguates the test trade. val sessionID = random63BitValue() progressTracker.currentStep = STARTING_BUY - send(DEMO_TOPIC, newPartnerAddr, 0, sessionID) + send(DEMO_TOPIC, recipient, 0, sessionID) val notary = serviceHub.networkMapCache.notaryNodes[0] - val buyer = TwoPartyTradeProtocol.Buyer(newPartnerAddr, notary.identity, amount, + val buyer = TwoPartyTradeProtocol.Buyer(recipient, notary.identity, amount, CommercialPaper.State::class.java, sessionID) // This invokes the trading protocol and out pops our finished transaction. @@ -316,7 +336,7 @@ ${Emoji.renderIfSupported(cpIssuance)}""") } } -private class TraderDemoProtocolSeller(val myAddress: HostAndPort, +private class TraderDemoProtocolSeller(val myAddr: Destination, val otherSide: SingleMessageRecipient, val amount: Amount>, override val progressTracker: ProgressTracker = TraderDemoProtocolSeller.tracker()) : ProtocolLogic() { @@ -392,7 +412,7 @@ private class TraderDemoProtocolSeller(val myAddress: HostAndPort, // Now make a dummy transaction that moves it to a new key, just to show that resolving dependencies works. val move: SignedTransaction = run { - val builder = TransactionType.General.Builder() + val builder = TransactionBuilder() CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy) builder.signWith(keyPair) val notarySignature = subProtocol(NotaryProtocol.Client(builder.toSignedTransaction(false))) diff --git a/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt b/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt index 489cf5b74a..4575cfc909 100644 --- a/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt +++ b/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt @@ -20,10 +20,11 @@ class TraderDemoTest { private fun runBuyer() { thread(true, false, null, "Buyer", -1, { runTraderDemo(arrayOf("--role", "BUYER"), true) }) - Thread.sleep(5000) + Thread.sleep(15000) } private fun runSeller() { + println("Running Seller") assertEquals(runTraderDemo(arrayOf("--role", "SELLER"), true), 0) } From a7ac54f28058b277dc6f8be18bd38e90511ba86f Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Wed, 8 Jun 2016 15:55:25 +0100 Subject: [PATCH 14/48] Removed thread waits from tests instead relying on a lock passed to the demo environment. --- src/main/kotlin/com/r3corda/demos/DemoNode.kt | 6 +++ src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 11 ++--- .../kotlin/com/r3corda/demos/TraderDemo.kt | 13 ++++-- .../com/r3corda/core/testing/IRSDemoTest.kt | 46 ++++++++----------- .../com/r3corda/core/testing/TradeDemoTest.kt | 12 ++--- 5 files changed, 43 insertions(+), 45 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/DemoNode.kt b/src/main/kotlin/com/r3corda/demos/DemoNode.kt index 6f04fbabea..508b2feb03 100644 --- a/src/main/kotlin/com/r3corda/demos/DemoNode.kt +++ b/src/main/kotlin/com/r3corda/demos/DemoNode.kt @@ -10,6 +10,7 @@ import com.r3corda.node.services.config.NodeConfiguration import com.r3corda.node.services.network.InMemoryMessagingNetwork import java.nio.file.Path import java.time.Clock +import java.util.concurrent.CountDownLatch val messageNetwork = InMemoryMessagingNetwork() @@ -24,4 +25,9 @@ class DemoNode(messagingService: MessagingService, dir: Path, p2pAddr: HostAndPo } override fun startMessagingService() = Unit +} + +class DemoConfig(useInMemoryMessaging: Boolean = false) { + val inMemory = useInMemoryMessaging + val nodeReady = CountDownLatch(1) } \ No newline at end of file diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index f06f54097d..0dacb98323 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -4,7 +4,6 @@ import com.google.common.net.HostAndPort import com.typesafe.config.ConfigFactory import com.r3corda.core.crypto.Party import com.r3corda.core.logElapsedTime -import com.r3corda.core.messaging.MessagingService import com.r3corda.node.internal.Node import com.r3corda.node.services.config.NodeConfiguration import com.r3corda.node.services.config.NodeConfigurationFromConfig @@ -35,7 +34,6 @@ import java.net.URL import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths -import java.time.Clock import java.util.* import kotlin.concurrent.fixedRateTimer import kotlin.system.exitProcess @@ -90,7 +88,7 @@ fun main(args: Array) { exitProcess(runIRSDemo(args)) } -fun runIRSDemo(args: Array, useInMemoryMessaging: Boolean = false): Int { +fun runIRSDemo(args: Array, demoNodeConfig: DemoConfig = DemoConfig()): Int { val parser = OptionParser() val demoArgs = setupArgs(parser) val options = try { @@ -154,7 +152,7 @@ fun runIRSDemo(args: Array, useInMemoryMessaging: Boolean = false): Int } try { - runNode(configureNodeParams(role, demoArgs, options), useInMemoryMessaging) + runNode(configureNodeParams(role, demoArgs, options), demoNodeConfig) } catch (e: NotSetupException) { println(e.message) return 1 @@ -241,8 +239,8 @@ private fun configureNodeParams(role: IRSDemoRole, args: DemoArgs, options: Opti return nodeParams } -private fun runNode(nodeParams : NodeParams, useInMemoryMessaging: Boolean) : Unit { - val node = when(useInMemoryMessaging) { +private fun runNode(nodeParams : NodeParams, demoNodeConfig: DemoConfig) : Unit { + val node = when(demoNodeConfig.inMemory) { true -> startDemoNode(nodeParams) false -> startNode(nodeParams) } @@ -255,6 +253,7 @@ private fun runNode(nodeParams : NodeParams, useInMemoryMessaging: Boolean) : Un runUploadRates("http://localhost:31341") } + demoNodeConfig.nodeReady.countDown() try { while (true) Thread.sleep(Long.MAX_VALUE) } catch(e: InterruptedException) { diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index 918b49a72b..a3effd7b68 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -32,7 +32,6 @@ import com.r3corda.protocols.NotaryProtocol import com.r3corda.protocols.TwoPartyTradeProtocol import com.typesafe.config.ConfigFactory import joptsimple.OptionParser -import joptsimple.OptionSet import java.io.Serializable import java.nio.file.Files import java.nio.file.Path @@ -78,7 +77,7 @@ fun main(args: Array) { exitProcess(runTraderDemo(args)) } -fun runTraderDemo(args: Array, useInMemoryMessaging: Boolean = false): Int { +fun runTraderDemo(args: Array, demoNodeConfig: DemoConfig = DemoConfig()): Int { val cashIssuerKey = generateKeyPair() val cashIssuer = Party("Trusted cash issuer", cashIssuerKey.public) val amount = 1000.DOLLARS `issued by` cashIssuer.ref(1) @@ -163,7 +162,7 @@ fun runTraderDemo(args: Array, useInMemoryMessaging: Boolean = false): I cashIssuer = party NodeInfo(ArtemisMessagingService.makeRecipient(theirNetAddr), party, setOf(NetworkMapService.Type)) - if(useInMemoryMessaging) { + if(demoNodeConfig.inMemory) { val handle = InMemoryMessagingNetwork.Handle(peerId, "Other Node") NodeInfo(handle, party, setOf(NetworkMapService.Type)) } else { @@ -173,7 +172,11 @@ fun runTraderDemo(args: Array, useInMemoryMessaging: Boolean = false): I val messageService = messageNetwork.createNodeWithID(false, id).start().get() // And now construct then start the node object. It takes a little while. val node = logElapsedTime("Node startup") { - Node(directory, myNetAddr, config, networkMapId, advertisedServices).setup().start() + if(demoNodeConfig.inMemory) { + DemoNode(messageService, directory, myNetAddr, config, networkMapId, advertisedServices).setup().start() + } else { + Node(directory, myNetAddr, config, networkMapId, advertisedServices).setup().start() + } } // TODO: Replace with a separate trusted cash issuer @@ -190,7 +193,7 @@ fun runTraderDemo(args: Array, useInMemoryMessaging: Boolean = false): I val dest: Destination val recipient: SingleMessageRecipient - if(useInMemoryMessaging) { + if(demoNodeConfig.inMemory) { recipient = InMemoryMessagingNetwork.Handle(peerId, "Other Node") dest = Destination(InMemoryMessagingNetwork.Handle(id, role.toString()), true) } else { diff --git a/src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index 5534ad7bb0..33a0a389f4 100644 --- a/src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -1,5 +1,6 @@ package com.r3corda.core.testing +import com.r3corda.demos.DemoConfig import com.r3corda.demos.runIRSDemo import kotlin.concurrent.thread import kotlin.test.assertEquals @@ -12,14 +13,12 @@ class IRSDemoTest { val dirA = Paths.get("./nodeA") val dirB = Paths.get("./nodeB") try { - setupNodeA(dirA) - setupNodeB(dirB) - startNodeA(dirA) - startNodeB(dirB) + setupNode(dirA, "NodeA") + setupNode(dirB, "NodeB") + startNode(dirA, "NodeA") + startNode(dirB, "NodeB") runTrade() runDateChange() - stopNodeA() - stopNodeB() } finally { cleanup(dirA) cleanup(dirB) @@ -27,30 +26,21 @@ class IRSDemoTest { } } -private fun setupNodeA(dir: Path) { - runIRSDemo(arrayOf("--role", "SetupNodeA", "--dir", dir.toString())) +private fun setupNode(dir: Path, nodeType: String) { + runIRSDemo(arrayOf("--role", "Setup" + nodeType, "--dir", dir.toString())) } -private fun setupNodeB(dir: Path) { - runIRSDemo(arrayOf("--role", "SetupNodeB", "--dir", dir.toString())) -} - -private fun startNodeA(dir: Path) { - thread(true, false, null, "NodeA", -1, { runIRSDemo(arrayOf("--role", "NodeA", "--dir", dir.toString()), true) }) - Thread.sleep(15000) -} - -private fun startNodeB(dir: Path) { - thread(true, false, null, "NodeB", -1, { runIRSDemo(arrayOf("--role", "NodeB", "--dir", dir.toString()), true) }) - Thread.sleep(15000) -} - -private fun stopNodeA() { - -} - -private fun stopNodeB() { - +private fun startNode(dir: Path, nodeType: String) { + val config = DemoConfig(true) + thread(true, false, null, nodeType, -1, { + try { + runIRSDemo(arrayOf("--role", nodeType, "--dir", dir.toString()), config) + } finally { + // Will only reach here during error or after node is stopped, so ensure lock is unlocked. + config.nodeReady.countDown() + } + }) + config.nodeReady.await() } private fun runTrade() { diff --git a/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt b/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt index 4575cfc909..ae75523736 100644 --- a/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt +++ b/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt @@ -1,10 +1,9 @@ package com.r3corda.core.testing +import com.r3corda.demos.DemoConfig import com.r3corda.demos.runTraderDemo import org.junit.Test -import java.nio.file.Path import java.nio.file.Paths -import kotlin.concurrent.thread import kotlin.test.assertEquals class TraderDemoTest { @@ -19,13 +18,14 @@ class TraderDemoTest { } private fun runBuyer() { - thread(true, false, null, "Buyer", -1, { runTraderDemo(arrayOf("--role", "BUYER"), true) }) - Thread.sleep(15000) + val config = DemoConfig(true) + runTraderDemo(arrayOf("--role", "BUYER"), config) + config.nodeReady.await() } private fun runSeller() { - println("Running Seller") - assertEquals(runTraderDemo(arrayOf("--role", "SELLER"), true), 0) + val config = DemoConfig(true) + assertEquals(runTraderDemo(arrayOf("--role", "SELLER"), config), 0) } private fun cleanup() { From 10fa86002d447a1469991bcca54fc5ee30124691 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 10:21:27 +0100 Subject: [PATCH 15/48] Fixed merge error causing IRSDemo to fail. --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 0dacb98323..ba7f1884c3 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -286,7 +286,7 @@ private fun sendJson(url: URL, data: String, method: String) : Boolean { connection.useCaches = false connection.requestMethod = method connection.connectTimeout = 5000 - connection.readTimeout = 10000 + connection.readTimeout = 15000 connection.setRequestProperty("Connection", "Keep-Alive") connection.setRequestProperty("Cache-Control", "no-cache") connection.setRequestProperty("Content-Type", "application/json") @@ -438,13 +438,13 @@ private fun startDemoNode(params : NodeParams) : Node { val myNetAddr = HostAndPort.fromString(params.address).withDefaultPort(Node.DEFAULT_PORT) val networkMapId = if (params.mapAddress.equals(params.address)) { // This node provides network map and notary services - advertisedServices = setOf(NetworkMapService.Type, NotaryService.Type) + advertisedServices = setOf(NetworkMapService.Type, SimpleNotaryService.Type) null } else { advertisedServices = setOf(NodeInterestRates.Type) val handle = InMemoryMessagingNetwork.Handle(createNodeAParams().id, params.defaultLegalName) - nodeInfo(handle, params.identityFile, setOf(NetworkMapService.Type, NotaryService.Type)) + nodeInfo(handle, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type)) } val messageService = messageNetwork.createNodeWithID(false, params.id).start().get() From 560989a914f348897b8987623f15cc31e086396b Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 10:31:42 +0100 Subject: [PATCH 16/48] Trader Demo test now works again. --- .../kotlin/com/r3corda/core/testing/TradeDemoTest.kt | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt b/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt index ae75523736..bc29139cb4 100644 --- a/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt +++ b/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt @@ -4,6 +4,7 @@ import com.r3corda.demos.DemoConfig import com.r3corda.demos.runTraderDemo import org.junit.Test import java.nio.file.Paths +import kotlin.concurrent.thread import kotlin.test.assertEquals class TraderDemoTest { @@ -19,7 +20,14 @@ class TraderDemoTest { private fun runBuyer() { val config = DemoConfig(true) - runTraderDemo(arrayOf("--role", "BUYER"), config) + thread(true, false, null, "Buyer", -1, { + try { + runTraderDemo(arrayOf("--role", "BUYER"), config) + } finally { + // Will only reach here during error or after node is stopped, so ensure lock is unlocked. + config.nodeReady.countDown() + } + }) config.nodeReady.await() } From 5986e785cf6df9e160a045d90ac0606c6f13a76d Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 11:00:13 +0100 Subject: [PATCH 17/48] Broke up the runIRSDemo function into smaller functions for readability. --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 104 +++++++++++-------- 1 file changed, 58 insertions(+), 46 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index ba7f1884c3..aa534fc3cd 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -103,60 +103,71 @@ fun runIRSDemo(args: Array, demoNodeConfig: DemoConfig = DemoConfig()): BriefLogFormatter.initVerbose("+demo.irsdemo", "+api-call", "+platform.deal", "-org.apache.activemq") val role = options.valueOf(demoArgs.roleArg)!! - if(role == IRSDemoRole.SetupNodeA) { - val nodeParams = configureNodeParams(IRSDemoRole.NodeA, demoArgs, options) - setup(nodeParams) - } else if(role == IRSDemoRole.SetupNodeB) { - val nodeParams = configureNodeParams(IRSDemoRole.NodeB, demoArgs, options) - setup(nodeParams) - } else if(role == IRSDemoRole.Trade) { - val tradeIdArgs = options.valuesOf(demoArgs.nonOptions) - if (tradeIdArgs.size > 0) { - val tradeId = tradeIdArgs[0] - val host = if (options.has(demoArgs.networkAddressArg)) { - options.valueOf(demoArgs.networkAddressArg) - } else { - "http://localhost:" + (Node.DEFAULT_PORT + 1) - } + return when (role) { + IRSDemoRole.SetupNodeA -> setup(configureNodeParams(IRSDemoRole.NodeA, demoArgs, options)) + IRSDemoRole.SetupNodeB -> setup(configureNodeParams(IRSDemoRole.NodeB, demoArgs, options)) + IRSDemoRole.NodeA -> runNode(role, demoArgs, options, demoNodeConfig) + IRSDemoRole.NodeB -> runNode(role, demoArgs, options, demoNodeConfig) + IRSDemoRole.Trade -> runTrade(demoArgs, options) + IRSDemoRole.Date -> runDateChange(demoArgs, options) + } +} - if (!runTrade(tradeId, host)) { - return 1 - } +private fun runTrade(demoArgs: DemoArgs, options: OptionSet): Int { + val tradeIdArgs = options.valuesOf(demoArgs.nonOptions) + if (tradeIdArgs.size > 0) { + val tradeId = tradeIdArgs[0] + val host = if (options.has(demoArgs.networkAddressArg)) { + options.valueOf(demoArgs.networkAddressArg) } else { - println("Please provide a trade ID") - return 1 + "http://localhost:" + (Node.DEFAULT_PORT + 1) } - } else if(role == IRSDemoRole.Date) { - val dateStrArgs = options.valuesOf(demoArgs.nonOptions) - if (dateStrArgs.size > 0) { - val dateStr = dateStrArgs[0] - val host = if (options.has(demoArgs.networkAddressArg)) { - options.valueOf(demoArgs.networkAddressArg) - } else { - "http://localhost:" + (Node.DEFAULT_PORT + 1) - } - if(!runDateChange(dateStr, host)) { - return 1 - } - } else { - println("Please provide a date") + if (!uploadTrade(tradeId, host)) { return 1 } } else { - // If these directory and identity file arguments aren't specified then we can assume a default setup and - // create everything that is needed without needing to run setup. - if(!options.has(demoArgs.dirArg) && !options.has(demoArgs.fakeTradeWithIdentityFile)) { - createNodeConfig(createNodeAParams()); - createNodeConfig(createNodeBParams()); + println("Please provide a trade ID") + return 1 + } + + return 0 +} + +private fun runDateChange(demoArgs: DemoArgs, options: OptionSet): Int { + val dateStrArgs = options.valuesOf(demoArgs.nonOptions) + if (dateStrArgs.size > 0) { + val dateStr = dateStrArgs[0] + val host = if (options.has(demoArgs.networkAddressArg)) { + options.valueOf(demoArgs.networkAddressArg) + } else { + "http://localhost:" + (Node.DEFAULT_PORT + 1) } - try { - runNode(configureNodeParams(role, demoArgs, options), demoNodeConfig) - } catch (e: NotSetupException) { - println(e.message) + if(!changeDate(dateStr, host)) { return 1 } + } else { + println("Please provide a date") + return 1 + } + + return 0 +} + +private fun runNode(role: IRSDemoRole, demoArgs: DemoArgs, options: OptionSet, demoNodeConfig: DemoConfig): Int { + // If these directory and identity file arguments aren't specified then we can assume a default setup and + // create everything that is needed without needing to run setup. + if(!options.has(demoArgs.dirArg) && !options.has(demoArgs.fakeTradeWithIdentityFile)) { + createNodeConfig(createNodeAParams()); + createNodeConfig(createNodeBParams()); + } + + try { + runNode(configureNodeParams(role, demoArgs, options), demoNodeConfig) + } catch (e: NotSetupException) { + println(e.message) + return 1 } return 0 @@ -178,11 +189,12 @@ private fun setupArgs(parser: OptionParser): DemoArgs { return args } -private fun setup(params: NodeParams) { +private fun setup(params: NodeParams): Int { createNodeConfig(params) + return 0 } -private fun runDateChange(date: String, host: String) : Boolean { +private fun changeDate(date: String, host: String) : Boolean { println("Changing date to " + date) val url = URL(host + "/api/irs/demodate") if(putJson(url, "\"" + date + "\"")) { @@ -194,7 +206,7 @@ private fun runDateChange(date: String, host: String) : Boolean { } } -private fun runTrade(tradeId: String, host: String) : Boolean { +private fun uploadTrade(tradeId: String, host: String) : Boolean { println("Uploading tradeID " + tradeId) val fileContents = IOUtils.toString(NodeParams::class.java.getResourceAsStream("example-irs-trade.json")) val tradeFile = fileContents.replace("tradeXXX", tradeId) From 3c11c26b125e0e73e7ff4996083298b8c3c64b34 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 11:51:33 +0100 Subject: [PATCH 18/48] Refactored IRSDemo to be more readable and to have minimal branches due to in memory mode in order to ensure tests are as similar to the real things as possible. --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 100 +++++++------------ 1 file changed, 38 insertions(+), 62 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index aa534fc3cd..339080752d 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -4,6 +4,8 @@ import com.google.common.net.HostAndPort import com.typesafe.config.ConfigFactory import com.r3corda.core.crypto.Party import com.r3corda.core.logElapsedTime +import com.r3corda.core.messaging.MessageRecipients +import com.r3corda.core.messaging.SingleMessageRecipient import com.r3corda.node.internal.Node import com.r3corda.node.services.config.NodeConfiguration import com.r3corda.node.services.config.NodeConfigurationFromConfig @@ -58,7 +60,6 @@ enum class IRSDemoRole { } private class NodeParams() { - var id: Int = -1 var dir : Path = Paths.get("") var address : String = "" var mapAddress: String = "" @@ -251,11 +252,13 @@ private fun configureNodeParams(role: IRSDemoRole, args: DemoArgs, options: Opti return nodeParams } -private fun runNode(nodeParams : NodeParams, demoNodeConfig: DemoConfig) : Unit { - val node = when(demoNodeConfig.inMemory) { - true -> startDemoNode(nodeParams) - false -> startNode(nodeParams) - } +private fun runNode(nodeParams: NodeParams, demoNodeConfig: DemoConfig) : Unit { + val networkMap = createRecipient(nodeParams.mapAddress, nodeParams, demoNodeConfig.inMemory) + val destinations = nodeParams.tradeWithAddrs.map({ + createRecipient(it, nodeParams, demoNodeConfig.inMemory) + }) + + val node = startNode(nodeParams, networkMap, destinations, demoNodeConfig.inMemory) // Register handlers for the demo AutoOfferProtocol.Handler.register(node) UpdateBusinessDayProtocol.Handler.register(node) @@ -273,6 +276,17 @@ private fun runNode(nodeParams : NodeParams, demoNodeConfig: DemoConfig) : Unit } } +private fun createRecipient(addr: String, params: NodeParams, inMemory: Boolean) : SingleMessageRecipient { + val hostAndPort = HostAndPort.fromString(addr).withDefaultPort(Node.DEFAULT_PORT) + return if(inMemory) { + // Assumption here is that all nodes run in memory and thus cannot share a port number. + val id = hostAndPort.port + InMemoryMessagingNetwork.Handle(id, "Node " + id) + } else { + ArtemisMessagingService.makeRecipient(hostAndPort) + } +} + private fun runUploadRates(host: String) { val fileContents = IOUtils.toString(NodeParams::class.java.getResource("example.rates.txt")) var timer : Timer? = null @@ -366,7 +380,6 @@ private fun uploadFile(url: URL, file: String) : Boolean { private fun createNodeAParams() : NodeParams { val params = NodeParams() - params.id = 0 params.dir = Paths.get("nodeA") params.address = "localhost" params.tradeWithAddrs = listOf("localhost:31340") @@ -377,7 +390,6 @@ private fun createNodeAParams() : NodeParams { private fun createNodeBParams() : NodeParams { val params = NodeParams() - params.id = 1 params.dir = Paths.get("nodeB") params.address = "localhost:31340" params.tradeWithAddrs = listOf("localhost") @@ -414,7 +426,7 @@ private fun getNodeConfig(params: NodeParams): NodeConfiguration { return loadConfigFile(configFile, params.defaultLegalName) } -private fun startNode(params : NodeParams) : Node { +private fun startNode(params : NodeParams, networkMap: SingleMessageRecipient, recipients: List, inMemory: Boolean) : Node { val config = getNodeConfig(params) val advertisedServices: Set val myNetAddr = HostAndPort.fromString(params.address).withDefaultPort(Node.DEFAULT_PORT) @@ -424,58 +436,34 @@ private fun startNode(params : NodeParams) : Node { null } else { advertisedServices = setOf(NodeInterestRates.Type) - nodeInfo(params.mapAddress, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type)) + nodeInfo(networkMap, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type)) } - val node = logElapsedTime("Node startup") { Node(params.dir, myNetAddr, config, networkMapId, - advertisedServices, DemoClock(), - listOf(InterestRateSwapAPI::class.java)).setup().start() } + val node = if(inMemory) { + // Port is ID for in memory since we assume in memory is all on the same machine, thus ports are unique. + val messageService = messageNetwork.createNodeWithID(false, myNetAddr.port).start().get() + logElapsedTime("Node startup") { DemoNode(messageService, params.dir, myNetAddr, config, networkMapId, + advertisedServices, DemoClock(), + listOf(InterestRateSwapAPI::class.java)).start() } + } else { + logElapsedTime("Node startup") { Node(params.dir, myNetAddr, config, networkMapId, + advertisedServices, DemoClock(), + listOf(InterestRateSwapAPI::class.java)).setup().start() } + } // TODO: This should all be replaced by the identity service being updated // as the network map changes. if (params.tradeWithAddrs.size != params.tradeWithIdentities.size) { throw IllegalArgumentException("Different number of peer addresses (${params.tradeWithAddrs.size}) and identities (${params.tradeWithIdentities.size})") } - for ((hostAndPortString, identityFile) in params.tradeWithAddrs.zip(params.tradeWithIdentities)) { - val peerId = nodeInfo(hostAndPortString, identityFile) + for ((recipient, identityFile) in recipients.zip(params.tradeWithIdentities)) { + val peerId = nodeInfo(recipient, identityFile) node.services.identityService.registerIdentity(peerId.identity) } return node } -private fun startDemoNode(params : NodeParams) : Node { - val config = createNodeConfig(params) - val advertisedServices: Set - val myNetAddr = HostAndPort.fromString(params.address).withDefaultPort(Node.DEFAULT_PORT) - val networkMapId = if (params.mapAddress.equals(params.address)) { - // This node provides network map and notary services - advertisedServices = setOf(NetworkMapService.Type, SimpleNotaryService.Type) - null - } else { - advertisedServices = setOf(NodeInterestRates.Type) - - val handle = InMemoryMessagingNetwork.Handle(createNodeAParams().id, params.defaultLegalName) - nodeInfo(handle, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type)) - } - - val messageService = messageNetwork.createNodeWithID(false, params.id).start().get() - val node = logElapsedTime("Node startup") { DemoNode(messageService, params.dir, myNetAddr, config, networkMapId, - advertisedServices, DemoClock(), - listOf(InterestRateSwapAPI::class.java)).setup().start() } - - // TODO: This should all be replaced by the identity service being updated - // as the network map changes. - val identityFile = params.tradeWithIdentities[0] - // Since in integration tests there are only two nodes with IDs 0 and 1, this hack will work - // TODO: Get Artemis working with two nodes in the same process or come up with a better solution - val handle = InMemoryMessagingNetwork.Handle(1 - params.id, "Other Node") - val peerId = nodeInfo(handle, identityFile) - node.services.identityService.registerIdentity(peerId.identity) - - return node -} - private fun getRoleDir(role: IRSDemoRole) : Path { when(role) { IRSDemoRole.NodeA -> return Paths.get("nodeA") @@ -486,23 +474,11 @@ private fun getRoleDir(role: IRSDemoRole) : Path { } } -private fun nodeInfo(hostAndPortString: String, identityFile: Path, advertisedServices: Set = emptySet()): NodeInfo { - try { - val addr = HostAndPort.fromString(hostAndPortString).withDefaultPort(Node.DEFAULT_PORT) - val path = identityFile - val party = Files.readAllBytes(path).deserialize() - return NodeInfo(ArtemisMessagingService.makeRecipient(addr), party, advertisedServices) - } catch (e: Exception) { - println("Could not find identify file $identityFile.") - throw e - } -} - -private fun nodeInfo(handle: InMemoryMessagingNetwork.Handle, identityFile: Path, advertisedServices: Set = emptySet()): NodeInfo { +private fun nodeInfo(recipient: SingleMessageRecipient, identityFile: Path, advertisedServices: Set = emptySet()): NodeInfo { try { val path = identityFile val party = Files.readAllBytes(path).deserialize() - return NodeInfo(handle, party, advertisedServices) + return NodeInfo(recipient, party, advertisedServices) } catch (e: Exception) { println("Could not find identify file $identityFile.") throw e @@ -521,7 +497,7 @@ private fun loadConfigFile(configFile: File, defaultLegalName: String): NodeConf private fun createIdentities(params: NodeParams, nodeConf: NodeConfiguration) { val mockNetwork = MockNetwork(false) - val node = MockNetwork.MockNode(params.dir, nodeConf, mockNetwork, null, setOf(NetworkMapService.Type, SimpleNotaryService.Type), params.id, null) + val node = MockNetwork.MockNode(params.dir, nodeConf, mockNetwork, null, setOf(NetworkMapService.Type, SimpleNotaryService.Type), 0, null) node.start() node.stop() } From 2d9989c5dfddda0db0afe45be66b35f5803d7f19 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 11:54:25 +0100 Subject: [PATCH 19/48] Rearranged code for improved reading locality. --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 104 +++++++++---------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 339080752d..ada22245d6 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -253,9 +253,9 @@ private fun configureNodeParams(role: IRSDemoRole, args: DemoArgs, options: Opti } private fun runNode(nodeParams: NodeParams, demoNodeConfig: DemoConfig) : Unit { - val networkMap = createRecipient(nodeParams.mapAddress, nodeParams, demoNodeConfig.inMemory) + val networkMap = createRecipient(nodeParams.mapAddress, demoNodeConfig.inMemory) val destinations = nodeParams.tradeWithAddrs.map({ - createRecipient(it, nodeParams, demoNodeConfig.inMemory) + createRecipient(it, demoNodeConfig.inMemory) }) val node = startNode(nodeParams, networkMap, destinations, demoNodeConfig.inMemory) @@ -276,7 +276,7 @@ private fun runNode(nodeParams: NodeParams, demoNodeConfig: DemoConfig) : Unit { } } -private fun createRecipient(addr: String, params: NodeParams, inMemory: Boolean) : SingleMessageRecipient { +private fun createRecipient(addr: String, inMemory: Boolean) : SingleMessageRecipient { val hostAndPort = HostAndPort.fromString(addr).withDefaultPort(Node.DEFAULT_PORT) return if(inMemory) { // Assumption here is that all nodes run in memory and thus cannot share a port number. @@ -287,6 +287,55 @@ private fun createRecipient(addr: String, params: NodeParams, inMemory: Boolean) } } +private fun startNode(params : NodeParams, networkMap: SingleMessageRecipient, recipients: List, inMemory: Boolean) : Node { + val config = getNodeConfig(params) + val advertisedServices: Set + val myNetAddr = HostAndPort.fromString(params.address).withDefaultPort(Node.DEFAULT_PORT) + val networkMapId = if (params.mapAddress.equals(params.address)) { + // This node provides network map and notary services + advertisedServices = setOf(NetworkMapService.Type, SimpleNotaryService.Type) + null + } else { + advertisedServices = setOf(NodeInterestRates.Type) + nodeInfo(networkMap, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type)) + } + + val node = if(inMemory) { + // Port is ID for in memory since we assume in memory is all on the same machine, thus ports are unique. + val messageService = messageNetwork.createNodeWithID(false, myNetAddr.port).start().get() + logElapsedTime("Node startup") { DemoNode(messageService, params.dir, myNetAddr, config, networkMapId, + advertisedServices, DemoClock(), + listOf(InterestRateSwapAPI::class.java)).start() } + } else { + logElapsedTime("Node startup") { Node(params.dir, myNetAddr, config, networkMapId, + advertisedServices, DemoClock(), + listOf(InterestRateSwapAPI::class.java)).start() } + } + + // TODO: This should all be replaced by the identity service being updated + // as the network map changes. + if (params.tradeWithAddrs.size != params.tradeWithIdentities.size) { + throw IllegalArgumentException("Different number of peer addresses (${params.tradeWithAddrs.size}) and identities (${params.tradeWithIdentities.size})") + } + for ((recipient, identityFile) in recipients.zip(params.tradeWithIdentities)) { + val peerId = nodeInfo(recipient, identityFile) + node.services.identityService.registerIdentity(peerId.identity) + } + + return node +} + +private fun nodeInfo(recipient: SingleMessageRecipient, identityFile: Path, advertisedServices: Set = emptySet()): NodeInfo { + try { + val path = identityFile + val party = Files.readAllBytes(path).deserialize() + return NodeInfo(recipient, party, advertisedServices) + } catch (e: Exception) { + println("Could not find identify file $identityFile.") + throw e + } +} + private fun runUploadRates(host: String) { val fileContents = IOUtils.toString(NodeParams::class.java.getResource("example.rates.txt")) var timer : Timer? = null @@ -426,44 +475,6 @@ private fun getNodeConfig(params: NodeParams): NodeConfiguration { return loadConfigFile(configFile, params.defaultLegalName) } -private fun startNode(params : NodeParams, networkMap: SingleMessageRecipient, recipients: List, inMemory: Boolean) : Node { - val config = getNodeConfig(params) - val advertisedServices: Set - val myNetAddr = HostAndPort.fromString(params.address).withDefaultPort(Node.DEFAULT_PORT) - val networkMapId = if (params.mapAddress.equals(params.address)) { - // This node provides network map and notary services - advertisedServices = setOf(NetworkMapService.Type, SimpleNotaryService.Type) - null - } else { - advertisedServices = setOf(NodeInterestRates.Type) - nodeInfo(networkMap, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type)) - } - - val node = if(inMemory) { - // Port is ID for in memory since we assume in memory is all on the same machine, thus ports are unique. - val messageService = messageNetwork.createNodeWithID(false, myNetAddr.port).start().get() - logElapsedTime("Node startup") { DemoNode(messageService, params.dir, myNetAddr, config, networkMapId, - advertisedServices, DemoClock(), - listOf(InterestRateSwapAPI::class.java)).start() } - } else { - logElapsedTime("Node startup") { Node(params.dir, myNetAddr, config, networkMapId, - advertisedServices, DemoClock(), - listOf(InterestRateSwapAPI::class.java)).setup().start() } - } - - // TODO: This should all be replaced by the identity service being updated - // as the network map changes. - if (params.tradeWithAddrs.size != params.tradeWithIdentities.size) { - throw IllegalArgumentException("Different number of peer addresses (${params.tradeWithAddrs.size}) and identities (${params.tradeWithIdentities.size})") - } - for ((recipient, identityFile) in recipients.zip(params.tradeWithIdentities)) { - val peerId = nodeInfo(recipient, identityFile) - node.services.identityService.registerIdentity(peerId.identity) - } - - return node -} - private fun getRoleDir(role: IRSDemoRole) : Path { when(role) { IRSDemoRole.NodeA -> return Paths.get("nodeA") @@ -474,17 +485,6 @@ private fun getRoleDir(role: IRSDemoRole) : Path { } } -private fun nodeInfo(recipient: SingleMessageRecipient, identityFile: Path, advertisedServices: Set = emptySet()): NodeInfo { - try { - val path = identityFile - val party = Files.readAllBytes(path).deserialize() - return NodeInfo(recipient, party, advertisedServices) - } catch (e: Exception) { - println("Could not find identify file $identityFile.") - throw e - } -} - private fun loadConfigFile(configFile: File, defaultLegalName: String): NodeConfiguration { if (!configFile.exists()) { createDefaultConfigFile(configFile, defaultLegalName) From 532416ec5a8db3a1850dd79abe95c8dfd49bef6e Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 11:57:35 +0100 Subject: [PATCH 20/48] Corrected name of the TraderDemoTest file. --- .../r3corda/core/testing/{TradeDemoTest.kt => TraderDemoTest.kt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/test/kotlin/com/r3corda/core/testing/{TradeDemoTest.kt => TraderDemoTest.kt} (100%) diff --git a/src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt b/src/test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt similarity index 100% rename from src/test/kotlin/com/r3corda/core/testing/TradeDemoTest.kt rename to src/test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt From 99fdacd0dc1475d40955280ae7f6e78438f40200 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 14:11:18 +0100 Subject: [PATCH 21/48] Integration tests now run separately from unit tests (with caveats described in the gradle file) --- build.gradle | 29 +++++++++++++++++-- .../com/r3corda/core/testing/IRSDemoTest.kt | 0 .../r3corda/core/testing/IRSSimulationTest.kt | 0 .../r3corda/core/testing/TraderDemoTest.kt | 0 4 files changed, 27 insertions(+), 2 deletions(-) rename src/{test => integration-test}/kotlin/com/r3corda/core/testing/IRSDemoTest.kt (100%) rename src/{test => integration-test}/kotlin/com/r3corda/core/testing/IRSSimulationTest.kt (100%) rename src/{test => integration-test}/kotlin/com/r3corda/core/testing/TraderDemoTest.kt (100%) diff --git a/build.gradle b/build.gradle index 6af144aef3..99ee6ad948 100644 --- a/build.gradle +++ b/build.gradle @@ -44,10 +44,23 @@ repositories { jcenter() } +sourceSets { + integrationTest { + kotlin { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/integration-test/kotlin') + } + } +} + //noinspection GroovyAssignabilityCheck configurations { // we don't want isolated.jar in classPath, since we want to test jar being dynamically loaded as an attachment runtime.exclude module: 'isolated' + + integrationTestCompile.extendsFrom testCompile + integrationTestRuntime.extendsFrom testRuntime } // This is required for quasar. I think. @@ -70,6 +83,10 @@ dependencies { testCompile 'junit:junit:4.12' testCompile 'org.assertj:assertj-core:3.4.1' testCompile 'com.pholser:junit-quickcheck-core:0.6' + + // Integration test helpers + integrationTestCompile 'junit:junit:4.12' + integrationTestCompile 'org.assertj:assertj-core:3.4.1' } // Package up the demo programs. @@ -99,8 +116,7 @@ task getTraderDemo(type: CreateStartScripts) { // Force windows script classpath to wildcard path to avoid the 'Command Line Is Too Long' issues // with generated scripts. Include Jolokia .war explicitly as this isn't picked up by wildcard -tasks.withType(CreateStartScripts) -{ +tasks.withType(CreateStartScripts) { doLast { windowsScript.text = windowsScript .readLines() @@ -109,6 +125,15 @@ tasks.withType(CreateStartScripts) } } +task integrationTest(type: Test) { + testClassesDir = sourceSets.integrationTest.output.classesDir + classpath = sourceSets.integrationTest.runtimeClasspath +} + +tasks.withType(Test) { + reports.html.destination = file("${reporting.baseDir}/${name}") +} + quasarScan.dependsOn('classes', 'core:classes', 'contracts:classes', 'node:classes') applicationDistribution.into("bin") { diff --git a/src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt similarity index 100% rename from src/test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt rename to src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt diff --git a/src/test/kotlin/com/r3corda/core/testing/IRSSimulationTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSSimulationTest.kt similarity index 100% rename from src/test/kotlin/com/r3corda/core/testing/IRSSimulationTest.kt rename to src/integration-test/kotlin/com/r3corda/core/testing/IRSSimulationTest.kt diff --git a/src/test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt similarity index 100% rename from src/test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt rename to src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt From ecdd0a23a2e88032f827ebf780ad9dcf6c522c43 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 14:11:47 +0100 Subject: [PATCH 22/48] Fixed bug in IRS demo where the node threads never exit during integration tests and cause other tests to fail. --- .../com/r3corda/core/testing/IRSDemoTest.kt | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index 33a0a389f4..833690320c 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -15,10 +15,12 @@ class IRSDemoTest { try { setupNode(dirA, "NodeA") setupNode(dirB, "NodeB") - startNode(dirA, "NodeA") - startNode(dirB, "NodeB") + val threadA = startNode(dirA, "NodeA") + val threadB = startNode(dirB, "NodeB") runTrade() runDateChange() + stopNode(threadA) + stopNode(threadB) } finally { cleanup(dirA) cleanup(dirB) @@ -30,9 +32,9 @@ private fun setupNode(dir: Path, nodeType: String) { runIRSDemo(arrayOf("--role", "Setup" + nodeType, "--dir", dir.toString())) } -private fun startNode(dir: Path, nodeType: String) { +private fun startNode(dir: Path, nodeType: String): Thread { val config = DemoConfig(true) - thread(true, false, null, nodeType, -1, { + val nodeThread = thread(true, false, null, nodeType, -1, { try { runIRSDemo(arrayOf("--role", nodeType, "--dir", dir.toString()), config) } finally { @@ -41,6 +43,7 @@ private fun startNode(dir: Path, nodeType: String) { } }) config.nodeReady.await() + return nodeThread } private fun runTrade() { @@ -51,6 +54,11 @@ private fun runDateChange() { assertEquals(runIRSDemo(arrayOf("--role", "Date", "2017-01-02")), 0) } +private fun stopNode(nodeThread: Thread) { + // The demo is designed to exit on interrupt + nodeThread.interrupt() +} + private fun cleanup(dir: Path) { println("Erasing: " + dir.toString()) dir.toFile().deleteRecursively() From bef4258430d4590647a990996c79b020faa529d0 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 14:12:27 +0100 Subject: [PATCH 23/48] Fixed Emoji crash where LANG envvar is not defined in particularly exotic setups (msys bash in Powershell for example) --- core/src/main/kotlin/com/r3corda/core/utilities/Emoji.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/com/r3corda/core/utilities/Emoji.kt b/core/src/main/kotlin/com/r3corda/core/utilities/Emoji.kt index 98fb29ad68..7a8ce93e84 100644 --- a/core/src/main/kotlin/com/r3corda/core/utilities/Emoji.kt +++ b/core/src/main/kotlin/com/r3corda/core/utilities/Emoji.kt @@ -4,7 +4,7 @@ package com.r3corda.core.utilities * A simple wrapper class that contains icons and support for printing them only when we're connected to a terminal. */ object Emoji { - val hasEmojiTerminal by lazy { System.getenv("TERM") != null && System.getenv("LANG").contains("UTF-8") } + val hasEmojiTerminal by lazy { System.getenv("TERM") != null && (System.getenv("LANG") != null) && System.getenv("LANG").contains("UTF-8") } const val CODE_DIAMOND = "\ud83d\udd37" const val CODE_BAG_OF_CASH = "\ud83d\udcb0" From 22dd36950c7f7ae8739c3b1fad94600b11fd6681 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 14:22:36 +0100 Subject: [PATCH 24/48] Moved the IRSSimulationTest back into the unit test directory. --- .../kotlin/com/r3corda/core/testing/IRSSimulationTest.kt | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/{integration-test => test}/kotlin/com/r3corda/core/testing/IRSSimulationTest.kt (100%) diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSSimulationTest.kt b/src/test/kotlin/com/r3corda/core/testing/IRSSimulationTest.kt similarity index 100% rename from src/integration-test/kotlin/com/r3corda/core/testing/IRSSimulationTest.kt rename to src/test/kotlin/com/r3corda/core/testing/IRSSimulationTest.kt From 03e2852880d4395e60044959089bce6a421bb049 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 16:42:06 +0100 Subject: [PATCH 25/48] The integration tests for demos now spawn a new JVM instead of using threads. Demos no longer need or contain any in memory node logic. --- .../com/r3corda/core/testing/IRSDemoTest.kt | 51 ++++++------ .../com/r3corda/core/testing/JVMSpawner.kt | 15 ++++ .../r3corda/core/testing/TraderDemoTest.kt | 29 +++---- src/main/kotlin/com/r3corda/demos/DemoNode.kt | 33 -------- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 45 ++++------- .../kotlin/com/r3corda/demos/TraderDemo.kt | 79 ++++--------------- 6 files changed, 81 insertions(+), 171 deletions(-) create mode 100644 src/integration-test/kotlin/com/r3corda/core/testing/JVMSpawner.kt delete mode 100644 src/main/kotlin/com/r3corda/demos/DemoNode.kt diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index 833690320c..e7812cd784 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -1,8 +1,5 @@ package com.r3corda.core.testing -import com.r3corda.demos.DemoConfig -import com.r3corda.demos.runIRSDemo -import kotlin.concurrent.thread import kotlin.test.assertEquals import org.junit.Test import java.nio.file.Path @@ -12,51 +9,53 @@ class IRSDemoTest { @Test fun `runs IRS demo`() { val dirA = Paths.get("./nodeA") val dirB = Paths.get("./nodeB") + var procA: Process? = null + var procB: Process? = null try { setupNode(dirA, "NodeA") setupNode(dirB, "NodeB") - val threadA = startNode(dirA, "NodeA") - val threadB = startNode(dirB, "NodeB") + procA = startNode(dirA, "NodeA") + procB = startNode(dirB, "NodeB") runTrade() runDateChange() - stopNode(threadA) - stopNode(threadB) } finally { + stopNode(procA) + stopNode(procB) cleanup(dirA) cleanup(dirB) } } } - private fun setupNode(dir: Path, nodeType: String) { - runIRSDemo(arrayOf("--role", "Setup" + nodeType, "--dir", dir.toString())) + val args = listOf("--role", "Setup" + nodeType, "--dir", dir.toString()) + val proc = spawn("com.r3corda.demos.IRSDemoKt", args) + proc.waitFor(); + assertEquals(proc.exitValue(), 0) } -private fun startNode(dir: Path, nodeType: String): Thread { - val config = DemoConfig(true) - val nodeThread = thread(true, false, null, nodeType, -1, { - try { - runIRSDemo(arrayOf("--role", nodeType, "--dir", dir.toString()), config) - } finally { - // Will only reach here during error or after node is stopped, so ensure lock is unlocked. - config.nodeReady.countDown() - } - }) - config.nodeReady.await() - return nodeThread +private fun startNode(dir: Path, nodeType: String): Process { + val args = listOf("--role", nodeType, "--dir", dir.toString()) + val proc = spawn("com.r3corda.demos.IRSDemoKt", args) + Thread.sleep(15000) + return proc } private fun runTrade() { - assertEquals(runIRSDemo(arrayOf("--role", "Trade", "trade1")), 0) + val args = listOf("--role", "Trade", "trade1") + val proc = spawn("com.r3corda.demos.IRSDemoKt", args) + proc.waitFor(); + assertEquals(proc.exitValue(), 0) } private fun runDateChange() { - assertEquals(runIRSDemo(arrayOf("--role", "Date", "2017-01-02")), 0) + val args = listOf("--role", "Date", "2017-01-02") + val proc = spawn("com.r3corda.demos.IRSDemoKt", args) + proc.waitFor(); + assertEquals(proc.exitValue(), 0) } -private fun stopNode(nodeThread: Thread) { - // The demo is designed to exit on interrupt - nodeThread.interrupt() +private fun stopNode(nodeProc: Process?) { + nodeProc?.destroy() } private fun cleanup(dir: Path) { diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/JVMSpawner.kt b/src/integration-test/kotlin/com/r3corda/core/testing/JVMSpawner.kt new file mode 100644 index 0000000000..7ddf11a160 --- /dev/null +++ b/src/integration-test/kotlin/com/r3corda/core/testing/JVMSpawner.kt @@ -0,0 +1,15 @@ +package com.r3corda.core.testing + +import java.nio.file.Paths + +fun spawn(className: String, args: List): Process { + val separator = System.getProperty("file.separator") + val classpath = System.getProperty("java.class.path") + val path = System.getProperty("java.home") + separator + "bin" + separator + "java" + val javaArgs = listOf(path, "-javaagent:lib/quasar.jar", "-cp", classpath, className) + val builder = ProcessBuilder(javaArgs + args) + builder.redirectError(Paths.get("error.$className.log").toFile()) + builder.inheritIO() + val process = builder.start(); + return process +} diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt index bc29139cb4..576ab9d0be 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt @@ -1,39 +1,34 @@ package com.r3corda.core.testing -import com.r3corda.demos.DemoConfig -import com.r3corda.demos.runTraderDemo import org.junit.Test import java.nio.file.Paths -import kotlin.concurrent.thread import kotlin.test.assertEquals class TraderDemoTest { @Test fun `runs trader demo`() { + var nodeProc: Process? = null try { - runBuyer() + nodeProc = runBuyer() runSeller() } finally { + nodeProc?.destroy() cleanup() } } } -private fun runBuyer() { - val config = DemoConfig(true) - thread(true, false, null, "Buyer", -1, { - try { - runTraderDemo(arrayOf("--role", "BUYER"), config) - } finally { - // Will only reach here during error or after node is stopped, so ensure lock is unlocked. - config.nodeReady.countDown() - } - }) - config.nodeReady.await() +private fun runBuyer(): Process { + val args = listOf("--role", "BUYER") + val proc = spawn("com.r3corda.demos.TraderDemoKt", args) + Thread.sleep(15000) + return proc } private fun runSeller() { - val config = DemoConfig(true) - assertEquals(runTraderDemo(arrayOf("--role", "SELLER"), config), 0) + val args = listOf("--role", "SELLER") + val proc = spawn("com.r3corda.demos.TraderDemoKt", args) + proc.waitFor(); + assertEquals(proc.exitValue(), 0) } private fun cleanup() { diff --git a/src/main/kotlin/com/r3corda/demos/DemoNode.kt b/src/main/kotlin/com/r3corda/demos/DemoNode.kt deleted file mode 100644 index 508b2feb03..0000000000 --- a/src/main/kotlin/com/r3corda/demos/DemoNode.kt +++ /dev/null @@ -1,33 +0,0 @@ -package com.r3corda.demos - -import com.google.common.net.HostAndPort -import com.r3corda.core.messaging.MessagingService -import com.r3corda.core.node.NodeInfo -import com.r3corda.core.node.services.ServiceType -import com.r3corda.node.internal.Node -import com.r3corda.node.serialization.NodeClock -import com.r3corda.node.services.config.NodeConfiguration -import com.r3corda.node.services.network.InMemoryMessagingNetwork -import java.nio.file.Path -import java.time.Clock -import java.util.concurrent.CountDownLatch - -val messageNetwork = InMemoryMessagingNetwork() - -class DemoNode(messagingService: MessagingService, dir: Path, p2pAddr: HostAndPort, config: NodeConfiguration, - networkMapAddress: NodeInfo?, advertisedServices: Set, - clock: Clock = NodeClock(), clientAPIs: List> = listOf()) -: Node(dir, p2pAddr, config, networkMapAddress, advertisedServices, clock, clientAPIs) { - - val messagingService = messagingService - override fun makeMessagingService(): MessagingService { - return messagingService - } - - override fun startMessagingService() = Unit -} - -class DemoConfig(useInMemoryMessaging: Boolean = false) { - val inMemory = useInMemoryMessaging - val nodeReady = CountDownLatch(1) -} \ No newline at end of file diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index ada22245d6..26a69eb1c3 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -89,7 +89,7 @@ fun main(args: Array) { exitProcess(runIRSDemo(args)) } -fun runIRSDemo(args: Array, demoNodeConfig: DemoConfig = DemoConfig()): Int { +fun runIRSDemo(args: Array): Int { val parser = OptionParser() val demoArgs = setupArgs(parser) val options = try { @@ -107,8 +107,8 @@ fun runIRSDemo(args: Array, demoNodeConfig: DemoConfig = DemoConfig()): return when (role) { IRSDemoRole.SetupNodeA -> setup(configureNodeParams(IRSDemoRole.NodeA, demoArgs, options)) IRSDemoRole.SetupNodeB -> setup(configureNodeParams(IRSDemoRole.NodeB, demoArgs, options)) - IRSDemoRole.NodeA -> runNode(role, demoArgs, options, demoNodeConfig) - IRSDemoRole.NodeB -> runNode(role, demoArgs, options, demoNodeConfig) + IRSDemoRole.NodeA -> runNode(role, demoArgs, options) + IRSDemoRole.NodeB -> runNode(role, demoArgs, options) IRSDemoRole.Trade -> runTrade(demoArgs, options) IRSDemoRole.Date -> runDateChange(demoArgs, options) } @@ -156,7 +156,7 @@ private fun runDateChange(demoArgs: DemoArgs, options: OptionSet): Int { return 0 } -private fun runNode(role: IRSDemoRole, demoArgs: DemoArgs, options: OptionSet, demoNodeConfig: DemoConfig): Int { +private fun runNode(role: IRSDemoRole, demoArgs: DemoArgs, options: OptionSet): Int { // If these directory and identity file arguments aren't specified then we can assume a default setup and // create everything that is needed without needing to run setup. if(!options.has(demoArgs.dirArg) && !options.has(demoArgs.fakeTradeWithIdentityFile)) { @@ -165,7 +165,7 @@ private fun runNode(role: IRSDemoRole, demoArgs: DemoArgs, options: OptionSet, d } try { - runNode(configureNodeParams(role, demoArgs, options), demoNodeConfig) + runNode(configureNodeParams(role, demoArgs, options)) } catch (e: NotSetupException) { println(e.message) return 1 @@ -252,13 +252,13 @@ private fun configureNodeParams(role: IRSDemoRole, args: DemoArgs, options: Opti return nodeParams } -private fun runNode(nodeParams: NodeParams, demoNodeConfig: DemoConfig) : Unit { - val networkMap = createRecipient(nodeParams.mapAddress, demoNodeConfig.inMemory) +private fun runNode(nodeParams: NodeParams) : Unit { + val networkMap = createRecipient(nodeParams.mapAddress) val destinations = nodeParams.tradeWithAddrs.map({ - createRecipient(it, demoNodeConfig.inMemory) + createRecipient(it) }) - val node = startNode(nodeParams, networkMap, destinations, demoNodeConfig.inMemory) + val node = startNode(nodeParams, networkMap, destinations) // Register handlers for the demo AutoOfferProtocol.Handler.register(node) UpdateBusinessDayProtocol.Handler.register(node) @@ -268,7 +268,6 @@ private fun runNode(nodeParams: NodeParams, demoNodeConfig: DemoConfig) : Unit { runUploadRates("http://localhost:31341") } - demoNodeConfig.nodeReady.countDown() try { while (true) Thread.sleep(Long.MAX_VALUE) } catch(e: InterruptedException) { @@ -276,18 +275,12 @@ private fun runNode(nodeParams: NodeParams, demoNodeConfig: DemoConfig) : Unit { } } -private fun createRecipient(addr: String, inMemory: Boolean) : SingleMessageRecipient { +private fun createRecipient(addr: String) : SingleMessageRecipient { val hostAndPort = HostAndPort.fromString(addr).withDefaultPort(Node.DEFAULT_PORT) - return if(inMemory) { - // Assumption here is that all nodes run in memory and thus cannot share a port number. - val id = hostAndPort.port - InMemoryMessagingNetwork.Handle(id, "Node " + id) - } else { - ArtemisMessagingService.makeRecipient(hostAndPort) - } + return ArtemisMessagingService.makeRecipient(hostAndPort) } -private fun startNode(params : NodeParams, networkMap: SingleMessageRecipient, recipients: List, inMemory: Boolean) : Node { +private fun startNode(params : NodeParams, networkMap: SingleMessageRecipient, recipients: List) : Node { val config = getNodeConfig(params) val advertisedServices: Set val myNetAddr = HostAndPort.fromString(params.address).withDefaultPort(Node.DEFAULT_PORT) @@ -300,17 +293,9 @@ private fun startNode(params : NodeParams, networkMap: SingleMessageRecipient, r nodeInfo(networkMap, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type)) } - val node = if(inMemory) { - // Port is ID for in memory since we assume in memory is all on the same machine, thus ports are unique. - val messageService = messageNetwork.createNodeWithID(false, myNetAddr.port).start().get() - logElapsedTime("Node startup") { DemoNode(messageService, params.dir, myNetAddr, config, networkMapId, - advertisedServices, DemoClock(), - listOf(InterestRateSwapAPI::class.java)).start() } - } else { - logElapsedTime("Node startup") { Node(params.dir, myNetAddr, config, networkMapId, - advertisedServices, DemoClock(), - listOf(InterestRateSwapAPI::class.java)).start() } - } + val node = logElapsedTime("Node startup") { Node(params.dir, myNetAddr, config, networkMapId, + advertisedServices, DemoClock(), + listOf(InterestRateSwapAPI::class.java)).start() } // TODO: This should all be replaced by the identity service being updated // as the network map changes. diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index a3effd7b68..8967f1f459 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -24,7 +24,6 @@ import com.r3corda.core.utilities.ProgressTracker import com.r3corda.node.internal.Node import com.r3corda.node.services.config.NodeConfigurationFromConfig import com.r3corda.node.services.messaging.ArtemisMessagingService -import com.r3corda.node.services.network.InMemoryMessagingNetwork import com.r3corda.node.services.network.NetworkMapService import com.r3corda.node.services.persistence.NodeAttachmentService import com.r3corda.node.services.transactions.SimpleNotaryService @@ -63,10 +62,6 @@ enum class Role { BUYER, SELLER } - -private class Destination constructor(addr: Any, inMemory: Boolean): Serializable { - val inMemory = inMemory - val addr = addr } // And this is the directory under the current working directory where each node will create its own server directory, @@ -77,7 +72,7 @@ fun main(args: Array) { exitProcess(runTraderDemo(args)) } -fun runTraderDemo(args: Array, demoNodeConfig: DemoConfig = DemoConfig()): Int { +fun runTraderDemo(args: Array): Int { val cashIssuerKey = generateKeyPair() val cashIssuer = Party("Trusted cash issuer", cashIssuerKey.public) val amount = 1000.DOLLARS `issued by` cashIssuer.ref(1) @@ -110,19 +105,6 @@ fun runTraderDemo(args: Array, demoNodeConfig: DemoConfig = DemoConfig() } ) - val peerId: Int; - val id: Int - when (role) { - Role.BUYER -> { - peerId = 1 - id = 0 - } - Role.SELLER -> { - peerId = 0 - id = 1 - } - } - // Suppress the Artemis MQ noise, and activate the demo logging. // // The first two strings correspond to the first argument to StateMachineManager.add() but the way we handle logging @@ -161,28 +143,9 @@ fun runTraderDemo(args: Array, demoNodeConfig: DemoConfig = DemoConfig() advertisedServices = emptySet() cashIssuer = party NodeInfo(ArtemisMessagingService.makeRecipient(theirNetAddr), party, setOf(NetworkMapService.Type)) - - if(demoNodeConfig.inMemory) { - val handle = InMemoryMessagingNetwork.Handle(peerId, "Other Node") - NodeInfo(handle, party, setOf(NetworkMapService.Type)) - } else { - NodeInfo(ArtemisMessagingService.makeRecipient(theirNetAddr), party, setOf(NetworkMapService.Type)) - } } - val messageService = messageNetwork.createNodeWithID(false, id).start().get() // And now construct then start the node object. It takes a little while. - val node = logElapsedTime("Node startup") { - if(demoNodeConfig.inMemory) { - DemoNode(messageService, directory, myNetAddr, config, networkMapId, advertisedServices).setup().start() - } else { - Node(directory, myNetAddr, config, networkMapId, advertisedServices).setup().start() - } - } - - // TODO: Replace with a separate trusted cash issuer - if (cashIssuer == null) { - cashIssuer = node.services.storageService.myLegalIdentity - } + val node = logElapsedTime("Node startup") { Node(directory, myNetAddr, config, networkMapId, advertisedServices).setup().start() } // What happens next depends on the role. The buyer sits around waiting for a trade to start. The seller role // will contact the buyer and actually make something happen. @@ -190,24 +153,14 @@ fun runTraderDemo(args: Array, demoNodeConfig: DemoConfig = DemoConfig() if (role == Role.BUYER) { runBuyer(node, amount) } else { - val dest: Destination - val recipient: SingleMessageRecipient - - if(demoNodeConfig.inMemory) { - recipient = InMemoryMessagingNetwork.Handle(peerId, "Other Node") - dest = Destination(InMemoryMessagingNetwork.Handle(id, role.toString()), true) - } else { - recipient = ArtemisMessagingService.makeRecipient(theirNetAddr) - dest = Destination(myNetAddr, false) - } - - runSeller(dest, node, recipient, amount)) + val recipient = ArtemisMessagingService.makeRecipient(theirNetAddr) + runSeller(myNetAddr, node, recipient, amount) } return 0 } -private fun runSeller(myAddr: Destination, node: Node, recipient: SingleMessageRecipient) { +private fun runSeller(myAddr: HostAndPort, node: Node, recipient: SingleMessageRecipient) { // The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash. // // The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an @@ -283,12 +236,8 @@ private class TraderDemoProtocolBuyer(private val attachmentsPath: Path, // As the seller initiates the two-party trade protocol, here, we will be the buyer. try { progressTracker.currentStep = WAITING_FOR_SELLER_TO_CONNECT - val origin: Destination = receive(DEMO_TOPIC, 0).validate { it } - val recipient: SingleMessageRecipient = if(origin.inMemory) { - origin.addr as InMemoryMessagingNetwork.Handle - } else { - ArtemisMessagingService.makeRecipient(origin.addr as HostAndPort) - } + val origin = receive(DEMO_TOPIC, 0).validate { it.withDefaultPort(Node.DEFAULT_PORT) } + val recipient = ArtemisMessagingService.makeRecipient(origin as HostAndPort) // The session ID disambiguates the test trade. val sessionID = random63BitValue() @@ -296,7 +245,7 @@ private class TraderDemoProtocolBuyer(private val attachmentsPath: Path, send(DEMO_TOPIC, recipient, 0, sessionID) val notary = serviceHub.networkMapCache.notaryNodes[0] - val buyer = TwoPartyTradeProtocol.Buyer(recipient, notary.identity, amount, + val buyer = TwoPartyTradeProtocol.Buyer(recipient, notary.identity, 1000.DOLLARS, CommercialPaper.State::class.java, sessionID) // This invokes the trading protocol and out pops our finished transaction. @@ -339,7 +288,7 @@ ${Emoji.renderIfSupported(cpIssuance)}""") } } -private class TraderDemoProtocolSeller(val myAddr: Destination, +private class TraderDemoProtocolSeller(val myAddr: HostAndPort, val otherSide: SingleMessageRecipient, val amount: Amount>, override val progressTracker: ProgressTracker = TraderDemoProtocolSeller.tracker()) : ProtocolLogic() { @@ -350,21 +299,21 @@ private class TraderDemoProtocolSeller(val myAddr: Destination, object SELF_ISSUING : ProgressTracker.Step("Got session ID back, issuing and timestamping some commercial paper") - object TRADING : ProgressTracker.Step("Starting the trade protocol") { - override fun childProgressTracker(): ProgressTracker = TwoPartyTradeProtocol.Seller.tracker() - } + object TRADING : ProgressTracker.Step("Starting the trade protocol") // We vend a progress tracker that already knows there's going to be a TwoPartyTradingProtocol involved at some // point: by setting up the tracker in advance, the user can see what's coming in more detail, instead of being // surprised when it appears as a new set of tasks below the current one. - fun tracker() = ProgressTracker(ANNOUNCING, SELF_ISSUING, TRADING) + fun tracker() = ProgressTracker(ANNOUNCING, SELF_ISSUING, TRADING).apply { + childrenFor[TRADING] = TwoPartyTradeProtocol.Seller.tracker() + } } @Suspendable override fun call() { progressTracker.currentStep = ANNOUNCING - val sessionID = sendAndReceive(DEMO_TOPIC, otherSide, 0, 0, myAddress).validate { it } + val sessionID = sendAndReceive(DEMO_TOPIC, otherSide, 0, 0, myAddr).validate { it } progressTracker.currentStep = SELF_ISSUING From 36a0ff0503b4398d6bb2e0c7c9056343ed01e482 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 17:28:46 +0100 Subject: [PATCH 26/48] Added an endpoint that allows querying for status of node. --- node/src/main/kotlin/com/r3corda/node/api/APIServer.kt | 9 +++++++++ .../kotlin/com/r3corda/node/internal/APIServerImpl.kt | 9 +++++++++ 2 files changed, 18 insertions(+) diff --git a/node/src/main/kotlin/com/r3corda/node/api/APIServer.kt b/node/src/main/kotlin/com/r3corda/node/api/APIServer.kt index 7e3979c16a..9883faab5f 100644 --- a/node/src/main/kotlin/com/r3corda/node/api/APIServer.kt +++ b/node/src/main/kotlin/com/r3corda/node/api/APIServer.kt @@ -11,6 +11,7 @@ import javax.ws.rs.GET import javax.ws.rs.Path import javax.ws.rs.Produces import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response /** * Top level interface to external interaction with the distributed ledger. @@ -30,6 +31,14 @@ interface APIServer { @Produces(MediaType.APPLICATION_JSON) fun serverTime(): LocalDateTime + /** + * Report whether this node is started up or not + */ + @GET + @Path("status") + @Produces(MediaType.TEXT_PLAIN) + fun status(): Response + /** * Query your "local" states (containing only outputs involving you) and return the hashes & indexes associated with them * to probably be later inflated by fetchLedgerTransactions() or fetchStates() although because immutable you can cache them diff --git a/node/src/main/kotlin/com/r3corda/node/internal/APIServerImpl.kt b/node/src/main/kotlin/com/r3corda/node/internal/APIServerImpl.kt index 4a8fd1f038..6f4eae6e6c 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/APIServerImpl.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/APIServerImpl.kt @@ -10,6 +10,7 @@ import com.r3corda.core.serialization.SerializedBytes import com.r3corda.node.api.* import java.time.LocalDateTime import java.util.* +import javax.ws.rs.core.Response import kotlin.reflect.KParameter import kotlin.reflect.jvm.javaType @@ -17,6 +18,14 @@ class APIServerImpl(val node: AbstractNode) : APIServer { override fun serverTime(): LocalDateTime = LocalDateTime.now(node.services.clock) + override fun status(): Response { + return if(node.started) { + Response.ok("started").build() + } else { + Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("not started").build() + } + } + override fun queryStates(query: StatesQuery): List { // We're going to hard code two options here for now and assume that all LinearStates are deals // Would like to maybe move to a model where we take something like a JEXL string, although don't want to develop From 9638ea5b9e29bd277e4bd5649241dfd0c0237ee4 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 9 Jun 2016 17:47:58 +0100 Subject: [PATCH 27/48] IRS demo test now uses the new status endpoint --- .../com/r3corda/core/testing/IRSDemoTest.kt | 40 +++++++++++++++++-- 1 file changed, 36 insertions(+), 4 deletions(-) diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index e7812cd784..c0e45c3bc0 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -2,9 +2,17 @@ package com.r3corda.core.testing import kotlin.test.assertEquals import org.junit.Test +import java.io.InputStreamReader +import java.net.ConnectException +import java.net.HttpURLConnection +import java.net.URL import java.nio.file.Path import java.nio.file.Paths +private class NodeDidNotStartException: Throwable { + constructor(message: String): super(message) {} +} + class IRSDemoTest { @Test fun `runs IRS demo`() { val dirA = Paths.get("./nodeA") @@ -14,8 +22,8 @@ class IRSDemoTest { try { setupNode(dirA, "NodeA") setupNode(dirB, "NodeB") - procA = startNode(dirA, "NodeA") - procB = startNode(dirB, "NodeB") + procA = startNode(dirA, "NodeA", "http://localhost:31338") + procB = startNode(dirB, "NodeB", "http://localhost:31341") runTrade() runDateChange() } finally { @@ -33,13 +41,37 @@ private fun setupNode(dir: Path, nodeType: String) { assertEquals(proc.exitValue(), 0) } -private fun startNode(dir: Path, nodeType: String): Process { +private fun startNode(dir: Path, nodeType: String, nodeAddr: String): Process { val args = listOf("--role", nodeType, "--dir", dir.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args) - Thread.sleep(15000) + waitForNode(nodeAddr) return proc } +// Todo: Move this to a library and use it in the trade demo +private fun waitForNode(nodeAddr: String) { + var retries = 0 + var respCode: Int = 404 + do { + retries++ + val url = URL(nodeAddr + "/api/status") + val err = try { + val conn = url.openConnection() as HttpURLConnection + conn.requestMethod = "GET" + respCode = conn.responseCode + InputStreamReader(conn.inputStream).readLines().joinToString { it } + } catch(e: ConnectException) { + // This is to be expected while it loads up + respCode = 404 + "Node hasn't started" + } + + if(retries > 200) { + throw NodeDidNotStartException("The node did not start: " + err) + } + } while (respCode != 200) +} + private fun runTrade() { val args = listOf("--role", "Trade", "trade1") val proc = spawn("com.r3corda.demos.IRSDemoKt", args) From 9973f8a33e9e4ade7d6e488f679c83b04f8d01aa Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Fri, 10 Jun 2016 09:48:48 +0100 Subject: [PATCH 28/48] Moved integration test utilities into a utility folder. --- .../com/r3corda/core/testing/IRSDemoTest.kt | 36 ++----------------- .../r3corda/core/testing/TraderDemoTest.kt | 4 ++- .../testing/{ => utilities}/JVMSpawner.kt | 2 +- .../r3corda/core/testing/utilities/NodeApi.kt | 33 +++++++++++++++++ 4 files changed, 40 insertions(+), 35 deletions(-) rename src/integration-test/kotlin/com/r3corda/core/testing/{ => utilities}/JVMSpawner.kt (93%) create mode 100644 src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index c0e45c3bc0..e55ffc01b2 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -1,18 +1,12 @@ package com.r3corda.core.testing +import com.r3corda.core.testing.utilities.spawn +import com.r3corda.core.testing.utilities.waitForNodeStartup import kotlin.test.assertEquals import org.junit.Test -import java.io.InputStreamReader -import java.net.ConnectException -import java.net.HttpURLConnection -import java.net.URL import java.nio.file.Path import java.nio.file.Paths -private class NodeDidNotStartException: Throwable { - constructor(message: String): super(message) {} -} - class IRSDemoTest { @Test fun `runs IRS demo`() { val dirA = Paths.get("./nodeA") @@ -44,34 +38,10 @@ private fun setupNode(dir: Path, nodeType: String) { private fun startNode(dir: Path, nodeType: String, nodeAddr: String): Process { val args = listOf("--role", nodeType, "--dir", dir.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args) - waitForNode(nodeAddr) + waitForNodeStartup(nodeAddr) return proc } -// Todo: Move this to a library and use it in the trade demo -private fun waitForNode(nodeAddr: String) { - var retries = 0 - var respCode: Int = 404 - do { - retries++ - val url = URL(nodeAddr + "/api/status") - val err = try { - val conn = url.openConnection() as HttpURLConnection - conn.requestMethod = "GET" - respCode = conn.responseCode - InputStreamReader(conn.inputStream).readLines().joinToString { it } - } catch(e: ConnectException) { - // This is to be expected while it loads up - respCode = 404 - "Node hasn't started" - } - - if(retries > 200) { - throw NodeDidNotStartException("The node did not start: " + err) - } - } while (respCode != 200) -} - private fun runTrade() { val args = listOf("--role", "Trade", "trade1") val proc = spawn("com.r3corda.demos.IRSDemoKt", args) diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt index 576ab9d0be..d74c97532c 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt @@ -1,5 +1,7 @@ package com.r3corda.core.testing +import com.r3corda.core.testing.utilities.spawn +import com.r3corda.core.testing.utilities.waitForNodeStartup import org.junit.Test import java.nio.file.Paths import kotlin.test.assertEquals @@ -20,7 +22,7 @@ class TraderDemoTest { private fun runBuyer(): Process { val args = listOf("--role", "BUYER") val proc = spawn("com.r3corda.demos.TraderDemoKt", args) - Thread.sleep(15000) + waitForNodeStartup("http://localhost:31338") return proc } diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/JVMSpawner.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt similarity index 93% rename from src/integration-test/kotlin/com/r3corda/core/testing/JVMSpawner.kt rename to src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt index 7ddf11a160..f88d55131b 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/JVMSpawner.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt @@ -1,4 +1,4 @@ -package com.r3corda.core.testing +package com.r3corda.core.testing.utilities import java.nio.file.Paths diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt new file mode 100644 index 0000000000..95ef74c27c --- /dev/null +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt @@ -0,0 +1,33 @@ +package com.r3corda.core.testing.utilities + +import java.io.InputStreamReader +import java.net.ConnectException +import java.net.HttpURLConnection +import java.net.URL + +class NodeDidNotStartException: Throwable { + constructor(message: String): super(message) {} +} + +fun waitForNodeStartup(nodeAddr: String) { + var retries = 0 + var respCode: Int + do { + retries++ + val url = URL(nodeAddr + "/api/status") + val err = try { + val conn = url.openConnection() as HttpURLConnection + conn.requestMethod = "GET" + respCode = conn.responseCode + InputStreamReader(conn.inputStream).readLines().joinToString { it } + } catch(e: ConnectException) { + // This is to be expected while it loads up + respCode = 404 + "Node hasn't started" + } + + if(retries > 200) { + throw NodeDidNotStartException("The node did not start: " + err) + } + } while (respCode != 200) +} \ No newline at end of file From 5a4215a31240540fe680c307d6cd0da47a37bcb4 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Fri, 10 Jun 2016 10:00:29 +0100 Subject: [PATCH 29/48] Fixed a change resulting from an incorrect merge. --- src/main/resources/com/r3corda/demos/example-irs-trade.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/resources/com/r3corda/demos/example-irs-trade.json b/src/main/resources/com/r3corda/demos/example-irs-trade.json index 5c602e3bbd..19cc8e8951 100644 --- a/src/main/resources/com/r3corda/demos/example-irs-trade.json +++ b/src/main/resources/com/r3corda/demos/example-irs-trade.json @@ -96,7 +96,7 @@ "addressForTransfers": "", "exposure": {}, "localBusinessDay": [ "London" , "NewYork" ], - "dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.currency.currencyCode.equals('GBP')) ? 365 : 360", + "dailyInterestAmount": "(CashAmount * InterestRate ) / (fixedLeg.notional.token.currencyCode.equals('GBP')) ? 365 : 360", "tradeID": "tradeXXX", "hashLegalDocs": "put hash here" } From 7a4a1363cbdf13a5f3993bdff3e5c4a84b13f584 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Fri, 10 Jun 2016 11:38:14 +0100 Subject: [PATCH 30/48] Removed unnecessary changes. --- .../main/kotlin/com/r3corda/node/internal/Node.kt | 2 +- src/main/kotlin/com/r3corda/demos/TraderDemo.kt | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt index f78fa99c71..6c3ddbdda4 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt @@ -52,7 +52,7 @@ class ConfigurationException(message: String) : Exception(message) * Listed clientAPI classes are assumed to have to take a single APIServer constructor parameter * @param clock The clock used within the node and by all protocols etc */ -open class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration, +class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration, networkMapAddress: NodeInfo?, advertisedServices: Set, clock: Clock = NodeClock(), val clientAPIs: List> = listOf()) : AbstractNode(dir, configuration, networkMapAddress, advertisedServices, clock) { diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index 8967f1f459..6bf3d06996 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -31,7 +31,6 @@ import com.r3corda.protocols.NotaryProtocol import com.r3corda.protocols.TwoPartyTradeProtocol import com.typesafe.config.ConfigFactory import joptsimple.OptionParser -import java.io.Serializable import java.nio.file.Files import java.nio.file.Path import java.nio.file.Paths @@ -62,7 +61,6 @@ enum class Role { BUYER, SELLER } -} // And this is the directory under the current working directory where each node will create its own server directory, // which holds things like checkpoints, keys, databases, message logs etc. @@ -145,7 +143,9 @@ fun runTraderDemo(args: Array): Int { NodeInfo(ArtemisMessagingService.makeRecipient(theirNetAddr), party, setOf(NetworkMapService.Type)) } // And now construct then start the node object. It takes a little while. - val node = logElapsedTime("Node startup") { Node(directory, myNetAddr, config, networkMapId, advertisedServices).setup().start() } + val node = logElapsedTime("Node startup") { + Node(directory, myNetAddr, config, networkMapId, advertisedServices).setup().start() + } // What happens next depends on the role. The buyer sits around waiting for a trade to start. The seller role // will contact the buyer and actually make something happen. @@ -160,7 +160,7 @@ fun runTraderDemo(args: Array): Int { return 0 } -private fun runSeller(myAddr: HostAndPort, node: Node, recipient: SingleMessageRecipient) { +private fun runSeller(myNetAddr: HostAndPort, node: Node, recipient: SingleMessageRecipient) { // The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash. // // The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an @@ -288,7 +288,7 @@ ${Emoji.renderIfSupported(cpIssuance)}""") } } -private class TraderDemoProtocolSeller(val myAddr: HostAndPort, +private class TraderDemoProtocolSeller(val myAddress: HostAndPort, val otherSide: SingleMessageRecipient, val amount: Amount>, override val progressTracker: ProgressTracker = TraderDemoProtocolSeller.tracker()) : ProtocolLogic() { @@ -313,7 +313,7 @@ private class TraderDemoProtocolSeller(val myAddr: HostAndPort, override fun call() { progressTracker.currentStep = ANNOUNCING - val sessionID = sendAndReceive(DEMO_TOPIC, otherSide, 0, 0, myAddr).validate { it } + val sessionID = sendAndReceive(DEMO_TOPIC, otherSide, 0, 0, myAddress).validate { it } progressTracker.currentStep = SELF_ISSUING From 609d80e63096abd3e30a91f9a8c6e99885698175 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Tue, 14 Jun 2016 00:33:31 +0100 Subject: [PATCH 31/48] Integration test module set correctly. --- .idea/modules.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/.idea/modules.xml b/.idea/modules.xml index 327bffd348..8a1bd10f31 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -21,6 +21,7 @@ + From 21dc8e7fd4d7d862cc5ba1342003e51e241570db Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Tue, 14 Jun 2016 11:18:11 +0100 Subject: [PATCH 32/48] Increased timeout time for reads during HTTP connections to avoid demo failing when date it set far in the future. --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 26a69eb1c3..af485386df 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -346,7 +346,7 @@ private fun sendJson(url: URL, data: String, method: String) : Boolean { connection.useCaches = false connection.requestMethod = method connection.connectTimeout = 5000 - connection.readTimeout = 15000 + connection.readTimeout = 60000 connection.setRequestProperty("Connection", "Keep-Alive") connection.setRequestProperty("Cache-Control", "no-cache") connection.setRequestProperty("Content-Type", "application/json") @@ -391,7 +391,7 @@ private fun uploadFile(url: URL, file: String) : Boolean { connection.useCaches = false connection.requestMethod = "POST" connection.connectTimeout = 5000 - connection.readTimeout = 5000 + connection.readTimeout = 60000 connection.setRequestProperty("Connection", "Keep-Alive") connection.setRequestProperty("Cache-Control", "no-cache") connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary) From 9a2a7165a50dea0d100194e17b8132081f6b7b5f Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Fri, 17 Jun 2016 11:00:33 +0100 Subject: [PATCH 33/48] Fixed merge errors. --- src/main/kotlin/com/r3corda/demos/TraderDemo.kt | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index 6bf3d06996..796f19f697 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -160,7 +160,10 @@ fun runTraderDemo(args: Array): Int { return 0 } -private fun runSeller(myNetAddr: HostAndPort, node: Node, recipient: SingleMessageRecipient) { +private fun runSeller(myNetAddr: HostAndPort, + node: Node, + recipient: SingleMessageRecipient, + amount: Amount>) { // The seller will sell some commercial paper to the buyer, who will pay with (self issued) cash. // // The CP sale transaction comes with a prospectus PDF, which will tag along for the ride in an @@ -179,7 +182,7 @@ private fun runSeller(myNetAddr: HostAndPort, node: Node, recipient: SingleMessa it.second.get() } } else { - val seller = TraderDemoProtocolSeller(myAddr, recipient, amount) + val seller = TraderDemoProtocolSeller(myNetAddr, recipient, amount) node.smm.add("demo.seller", seller).get() } @@ -245,7 +248,7 @@ private class TraderDemoProtocolBuyer(private val attachmentsPath: Path, send(DEMO_TOPIC, recipient, 0, sessionID) val notary = serviceHub.networkMapCache.notaryNodes[0] - val buyer = TwoPartyTradeProtocol.Buyer(recipient, notary.identity, 1000.DOLLARS, + val buyer = TwoPartyTradeProtocol.Buyer(recipient, notary.identity, amount, CommercialPaper.State::class.java, sessionID) // This invokes the trading protocol and out pops our finished transaction. @@ -299,14 +302,14 @@ private class TraderDemoProtocolSeller(val myAddress: HostAndPort, object SELF_ISSUING : ProgressTracker.Step("Got session ID back, issuing and timestamping some commercial paper") - object TRADING : ProgressTracker.Step("Starting the trade protocol") + object TRADING : ProgressTracker.Step("Starting the trade protocol") { + override fun childProgressTracker(): ProgressTracker = TwoPartyTradeProtocol.Seller.tracker() + } // We vend a progress tracker that already knows there's going to be a TwoPartyTradingProtocol involved at some // point: by setting up the tracker in advance, the user can see what's coming in more detail, instead of being // surprised when it appears as a new set of tasks below the current one. - fun tracker() = ProgressTracker(ANNOUNCING, SELF_ISSUING, TRADING).apply { - childrenFor[TRADING] = TwoPartyTradeProtocol.Seller.tracker() - } + fun tracker() = ProgressTracker(ANNOUNCING, SELF_ISSUING, TRADING) } @Suspendable From ad45b5deafa25468fb5936e5f96dc31d496bdd9a Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Fri, 17 Jun 2016 11:01:09 +0100 Subject: [PATCH 34/48] Tests now fail if spawned processes take too long to finish. --- .idea/modules.xml | 1 + .../kotlin/com/r3corda/core/testing/IRSDemoTest.kt | 7 ++++--- .../kotlin/com/r3corda/core/testing/TraderDemoTest.kt | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/.idea/modules.xml b/.idea/modules.xml index 8a1bd10f31..e38ea5ee5e 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -22,6 +22,7 @@ + diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index e55ffc01b2..48ce8d1a1b 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -6,6 +6,7 @@ import kotlin.test.assertEquals import org.junit.Test import java.nio.file.Path import java.nio.file.Paths +import java.util.concurrent.TimeUnit class IRSDemoTest { @Test fun `runs IRS demo`() { @@ -31,7 +32,7 @@ class IRSDemoTest { private fun setupNode(dir: Path, nodeType: String) { val args = listOf("--role", "Setup" + nodeType, "--dir", dir.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args) - proc.waitFor(); + assertEquals(proc.waitFor(30, TimeUnit.SECONDS), true); assertEquals(proc.exitValue(), 0) } @@ -45,14 +46,14 @@ private fun startNode(dir: Path, nodeType: String, nodeAddr: String): Process { private fun runTrade() { val args = listOf("--role", "Trade", "trade1") val proc = spawn("com.r3corda.demos.IRSDemoKt", args) - proc.waitFor(); + assertEquals(proc.waitFor(30, TimeUnit.SECONDS), true); assertEquals(proc.exitValue(), 0) } private fun runDateChange() { val args = listOf("--role", "Date", "2017-01-02") val proc = spawn("com.r3corda.demos.IRSDemoKt", args) - proc.waitFor(); + assertEquals(proc.waitFor(30, TimeUnit.SECONDS), true); assertEquals(proc.exitValue(), 0) } diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt index d74c97532c..e9073d6c5f 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt @@ -4,6 +4,7 @@ import com.r3corda.core.testing.utilities.spawn import com.r3corda.core.testing.utilities.waitForNodeStartup import org.junit.Test import java.nio.file.Paths +import java.util.concurrent.TimeUnit import kotlin.test.assertEquals class TraderDemoTest { @@ -29,7 +30,7 @@ private fun runBuyer(): Process { private fun runSeller() { val args = listOf("--role", "SELLER") val proc = spawn("com.r3corda.demos.TraderDemoKt", args) - proc.waitFor(); + assertEquals(proc.waitFor(30, TimeUnit.SECONDS), true); assertEquals(proc.exitValue(), 0) } From 5858ff5c45ee4e3a0aa652354091e61c7dc9e168 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Fri, 17 Jun 2016 11:38:13 +0100 Subject: [PATCH 35/48] Ensured that test passes under correct conditions, which may be slow, but also that the node lives until the end. --- .../kotlin/com/r3corda/core/testing/IRSDemoTest.kt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index 48ce8d1a1b..a81a63a6f0 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -32,7 +32,7 @@ class IRSDemoTest { private fun setupNode(dir: Path, nodeType: String) { val args = listOf("--role", "Setup" + nodeType, "--dir", dir.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args) - assertEquals(proc.waitFor(30, TimeUnit.SECONDS), true); + assertEquals(proc.waitFor(2, TimeUnit.MINUTES), true); assertEquals(proc.exitValue(), 0) } @@ -46,18 +46,19 @@ private fun startNode(dir: Path, nodeType: String, nodeAddr: String): Process { private fun runTrade() { val args = listOf("--role", "Trade", "trade1") val proc = spawn("com.r3corda.demos.IRSDemoKt", args) - assertEquals(proc.waitFor(30, TimeUnit.SECONDS), true); + assertEquals(proc.waitFor(2, TimeUnit.MINUTES), true); assertEquals(proc.exitValue(), 0) } private fun runDateChange() { val args = listOf("--role", "Date", "2017-01-02") val proc = spawn("com.r3corda.demos.IRSDemoKt", args) - assertEquals(proc.waitFor(30, TimeUnit.SECONDS), true); + assertEquals(proc.waitFor(2, TimeUnit.MINUTES), true); assertEquals(proc.exitValue(), 0) } private fun stopNode(nodeProc: Process?) { + assertEquals(nodeProc?.isAlive, true) nodeProc?.destroy() } From 9d4f75f24171f240291c9af958210a865d7f5b14 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Fri, 17 Jun 2016 11:44:40 +0100 Subject: [PATCH 36/48] Improved readability and brevity of hasEmojiTerminal. --- core/src/main/kotlin/com/r3corda/core/utilities/Emoji.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/com/r3corda/core/utilities/Emoji.kt b/core/src/main/kotlin/com/r3corda/core/utilities/Emoji.kt index 7a8ce93e84..864cb4c044 100644 --- a/core/src/main/kotlin/com/r3corda/core/utilities/Emoji.kt +++ b/core/src/main/kotlin/com/r3corda/core/utilities/Emoji.kt @@ -4,7 +4,7 @@ package com.r3corda.core.utilities * A simple wrapper class that contains icons and support for printing them only when we're connected to a terminal. */ object Emoji { - val hasEmojiTerminal by lazy { System.getenv("TERM") != null && (System.getenv("LANG") != null) && System.getenv("LANG").contains("UTF-8") } + val hasEmojiTerminal by lazy { System.getenv("TERM") != null && (System.getenv("LANG")?.contains("UTF-8") == true) } const val CODE_DIAMOND = "\ud83d\udd37" const val CODE_BAG_OF_CASH = "\ud83d\udcb0" From 5bf5e37572aa7d643a0bc445762cac1d9cfc4a18 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Fri, 17 Jun 2016 19:17:27 +0100 Subject: [PATCH 37/48] Demos now fully handle process managment except in the case where the process is killed with something like pkill or the JVM being ended by task manager. --- .idea/modules.xml | 1 - .../com/r3corda/core/testing/IRSDemoTest.kt | 17 ++++++++-------- .../r3corda/core/testing/TraderDemoTest.kt | 16 +++++++++------ .../core/testing/utilities/JVMSpawner.kt | 20 ++++++++++++++++++- .../r3corda/core/testing/utilities/NodeApi.kt | 17 +++++++++++++++- 5 files changed, 53 insertions(+), 18 deletions(-) diff --git a/.idea/modules.xml b/.idea/modules.xml index e38ea5ee5e..8a1bd10f31 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -22,7 +22,6 @@ - diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index a81a63a6f0..2a0e485a7d 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -1,12 +1,10 @@ package com.r3corda.core.testing -import com.r3corda.core.testing.utilities.spawn -import com.r3corda.core.testing.utilities.waitForNodeStartup +import com.r3corda.core.testing.utilities.* import kotlin.test.assertEquals import org.junit.Test import java.nio.file.Path import java.nio.file.Paths -import java.util.concurrent.TimeUnit class IRSDemoTest { @Test fun `runs IRS demo`() { @@ -32,34 +30,35 @@ class IRSDemoTest { private fun setupNode(dir: Path, nodeType: String) { val args = listOf("--role", "Setup" + nodeType, "--dir", dir.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args) - assertEquals(proc.waitFor(2, TimeUnit.MINUTES), true); + assertExitOrKill(proc) assertEquals(proc.exitValue(), 0) } private fun startNode(dir: Path, nodeType: String, nodeAddr: String): Process { val args = listOf("--role", nodeType, "--dir", dir.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args) - waitForNodeStartup(nodeAddr) + ensureNodeStartsOrKill(proc, nodeAddr) return proc } private fun runTrade() { val args = listOf("--role", "Trade", "trade1") val proc = spawn("com.r3corda.demos.IRSDemoKt", args) - assertEquals(proc.waitFor(2, TimeUnit.MINUTES), true); + assertExitOrKill(proc) assertEquals(proc.exitValue(), 0) } private fun runDateChange() { val args = listOf("--role", "Date", "2017-01-02") val proc = spawn("com.r3corda.demos.IRSDemoKt", args) - assertEquals(proc.waitFor(2, TimeUnit.MINUTES), true); + assertExitOrKill(proc) assertEquals(proc.exitValue(), 0) } private fun stopNode(nodeProc: Process?) { - assertEquals(nodeProc?.isAlive, true) - nodeProc?.destroy() + if(nodeProc != null) { + assertAliveAndKill(nodeProc) + } } private fun cleanup(dir: Path) { diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt index e9073d6c5f..2e492f5fd3 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt @@ -1,40 +1,44 @@ package com.r3corda.core.testing +import com.r3corda.core.testing.utilities.assertExitOrKill +import com.r3corda.core.testing.utilities.ensureNodeStartsOrKill import com.r3corda.core.testing.utilities.spawn -import com.r3corda.core.testing.utilities.waitForNodeStartup import org.junit.Test import java.nio.file.Paths -import java.util.concurrent.TimeUnit import kotlin.test.assertEquals class TraderDemoTest { @Test fun `runs trader demo`() { var nodeProc: Process? = null try { + cleanupFiles() nodeProc = runBuyer() runSeller() } finally { nodeProc?.destroy() - cleanup() + cleanupFiles() } } } private fun runBuyer(): Process { + println("Running Buyer") val args = listOf("--role", "BUYER") val proc = spawn("com.r3corda.demos.TraderDemoKt", args) - waitForNodeStartup("http://localhost:31338") + ensureNodeStartsOrKill(proc, "http://localhost:31338") return proc } private fun runSeller() { + println("Running Seller") val args = listOf("--role", "SELLER") val proc = spawn("com.r3corda.demos.TraderDemoKt", args) - assertEquals(proc.waitFor(30, TimeUnit.SECONDS), true); + assertExitOrKill(proc); assertEquals(proc.exitValue(), 0) } -private fun cleanup() { +private fun cleanupFiles() { + println("Cleaning up TraderDemoTest files") val dir = Paths.get("trader-demo") println("Erasing " + dir) dir.toFile().deleteRecursively() diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt index f88d55131b..b1495e3512 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt @@ -1,6 +1,8 @@ package com.r3corda.core.testing.utilities import java.nio.file.Paths +import java.util.concurrent.TimeUnit +import kotlin.test.assertEquals fun spawn(className: String, args: List): Process { val separator = System.getProperty("file.separator") @@ -9,7 +11,23 @@ fun spawn(className: String, args: List): Process { val javaArgs = listOf(path, "-javaagent:lib/quasar.jar", "-cp", classpath, className) val builder = ProcessBuilder(javaArgs + args) builder.redirectError(Paths.get("error.$className.log").toFile()) - builder.inheritIO() val process = builder.start(); return process } + +fun assertExitOrKill(proc: Process) { + try { + assertEquals(proc.waitFor(2, TimeUnit.MINUTES), true) + } catch (e: Throwable) { + proc.destroy() + throw e + } +} + +fun assertAliveAndKill(proc: Process) { + try { + assertEquals(proc.isAlive, true) + } finally { + proc.destroy() + } +} \ No newline at end of file diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt index 95ef74c27c..e206bb25bb 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt @@ -2,14 +2,26 @@ package com.r3corda.core.testing.utilities import java.io.InputStreamReader import java.net.ConnectException +import java.net.SocketException import java.net.HttpURLConnection import java.net.URL +import kotlin.test.assertEquals class NodeDidNotStartException: Throwable { constructor(message: String): super(message) {} } -fun waitForNodeStartup(nodeAddr: String) { +fun ensureNodeStartsOrKill(proc: Process, nodeAddr: String) { + try { + assertEquals(proc.isAlive, true) + waitForNodeStartup(nodeAddr) + } catch (e: Exception) { + proc.destroy() + throw e + } +} + +private fun waitForNodeStartup(nodeAddr: String) { var retries = 0 var respCode: Int do { @@ -24,6 +36,9 @@ fun waitForNodeStartup(nodeAddr: String) { // This is to be expected while it loads up respCode = 404 "Node hasn't started" + } catch(e: SocketException) { + respCode = -1 + "Could not connect: ${e.toString()}" } if(retries > 200) { From 68867d21bb81fa585fb14502b012d29209f3aedf Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Tue, 21 Jun 2016 14:56:37 +0100 Subject: [PATCH 38/48] Fixed merge conflict problems. --- src/main/kotlin/com/r3corda/demos/TraderDemo.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index 796f19f697..ec93fe7780 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -72,8 +72,6 @@ fun main(args: Array) { fun runTraderDemo(args: Array): Int { val cashIssuerKey = generateKeyPair() - val cashIssuer = Party("Trusted cash issuer", cashIssuerKey.public) - val amount = 1000.DOLLARS `issued by` cashIssuer.ref(1) val parser = OptionParser() val roleArg = parser.accepts("role").withRequiredArg().ofType(Role::class.java).required() @@ -142,11 +140,17 @@ fun runTraderDemo(args: Array): Int { cashIssuer = party NodeInfo(ArtemisMessagingService.makeRecipient(theirNetAddr), party, setOf(NetworkMapService.Type)) } + // And now construct then start the node object. It takes a little while. val node = logElapsedTime("Node startup") { Node(directory, myNetAddr, config, networkMapId, advertisedServices).setup().start() } + // TODO: Replace with a separate trusted cash issuer + if (cashIssuer == null) { + cashIssuer = node.services.storageService.myLegalIdentity + } + // What happens next depends on the role. The buyer sits around waiting for a trade to start. The seller role // will contact the buyer and actually make something happen. val amount = 1000.DOLLARS `issued by` cashIssuer.ref(0) // Note: "0" has to match the reference used in the wallet filler @@ -367,7 +371,7 @@ private class TraderDemoProtocolSeller(val myAddress: HostAndPort, // Now make a dummy transaction that moves it to a new key, just to show that resolving dependencies works. val move: SignedTransaction = run { - val builder = TransactionBuilder() + val builder = TransactionType.General.Builder() CommercialPaper().generateMove(builder, issuance.tx.outRef(0), ownedBy) builder.signWith(keyPair) val notarySignature = subProtocol(NotaryProtocol.Client(builder.toSignedTransaction(false))) From b52f344eb3cb1ed9ba413efb7f2e4eb93f77e33c Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Tue, 21 Jun 2016 16:48:43 +0100 Subject: [PATCH 39/48] Ensured that nodes are killed by process.destroyForcibly. Added random port numbers to test. --- .../com/r3corda/core/testing/IRSDemoTest.kt | 29 ++++++++++++------- .../r3corda/core/testing/TraderDemoTest.kt | 2 +- .../core/testing/utilities/JVMSpawner.kt | 5 ++-- .../r3corda/core/testing/utilities/NodeApi.kt | 16 ++++++---- 4 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index 2a0e485a7d..229d74e406 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -1,5 +1,6 @@ package com.r3corda.core.testing +import com.google.common.net.HostAndPort import com.r3corda.core.testing.utilities.* import kotlin.test.assertEquals import org.junit.Test @@ -8,6 +9,8 @@ import java.nio.file.Paths class IRSDemoTest { @Test fun `runs IRS demo`() { + val nodeAddrA = freeLocalHostAndPort() + val apiAddrA = HostAndPort.fromString("${nodeAddrA.hostText}:${nodeAddrA.port + 1}") val dirA = Paths.get("./nodeA") val dirB = Paths.get("./nodeB") var procA: Process? = null @@ -15,10 +18,10 @@ class IRSDemoTest { try { setupNode(dirA, "NodeA") setupNode(dirB, "NodeB") - procA = startNode(dirA, "NodeA", "http://localhost:31338") - procB = startNode(dirB, "NodeB", "http://localhost:31341") - runTrade() - runDateChange() + procA = startNode(dirA, "NodeA", nodeAddrA) + procB = startNode(dirB, "NodeB", freeLocalHostAndPort()) + runTrade(apiAddrA) + runDateChange(apiAddrA) } finally { stopNode(procA) stopNode(procB) @@ -28,28 +31,33 @@ class IRSDemoTest { } } private fun setupNode(dir: Path, nodeType: String) { + println("Running setup for $nodeType") val args = listOf("--role", "Setup" + nodeType, "--dir", dir.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args) assertExitOrKill(proc) assertEquals(proc.exitValue(), 0) } -private fun startNode(dir: Path, nodeType: String, nodeAddr: String): Process { - val args = listOf("--role", nodeType, "--dir", dir.toString()) +private fun startNode(dir: Path, nodeType: String, nodeAddr: HostAndPort): Process { + println("Running node $nodeType") + println("Node addr: ${nodeAddr.toString()}") + val args = listOf("--role", nodeType, "--dir", dir.toString(), "--network-address", nodeAddr.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args) ensureNodeStartsOrKill(proc, nodeAddr) return proc } -private fun runTrade() { - val args = listOf("--role", "Trade", "trade1") +private fun runTrade(nodeAddr: HostAndPort) { + println("Running trade") + val args = listOf("--role", "Trade", "trade1", "--network-address", "http://" + nodeAddr.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args) assertExitOrKill(proc) assertEquals(proc.exitValue(), 0) } -private fun runDateChange() { - val args = listOf("--role", "Date", "2017-01-02") +private fun runDateChange(nodeAddr: HostAndPort) { + println("Running date change") + val args = listOf("--role", "Date", "2017-01-02", "--network-address", "http://" + nodeAddr.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args) assertExitOrKill(proc) assertEquals(proc.exitValue(), 0) @@ -57,6 +65,7 @@ private fun runDateChange() { private fun stopNode(nodeProc: Process?) { if(nodeProc != null) { + println("Stopping node") assertAliveAndKill(nodeProc) } } diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt index 2e492f5fd3..62ab740729 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt @@ -25,7 +25,7 @@ private fun runBuyer(): Process { println("Running Buyer") val args = listOf("--role", "BUYER") val proc = spawn("com.r3corda.demos.TraderDemoKt", args) - ensureNodeStartsOrKill(proc, "http://localhost:31338") + ensureNodeStartsOrKill(proc, freeLocalHostAndPort()) return proc } diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt index b1495e3512..bfc7b3f8f2 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt @@ -11,6 +11,7 @@ fun spawn(className: String, args: List): Process { val javaArgs = listOf(path, "-javaagent:lib/quasar.jar", "-cp", classpath, className) val builder = ProcessBuilder(javaArgs + args) builder.redirectError(Paths.get("error.$className.log").toFile()) + builder.inheritIO() val process = builder.start(); return process } @@ -19,7 +20,7 @@ fun assertExitOrKill(proc: Process) { try { assertEquals(proc.waitFor(2, TimeUnit.MINUTES), true) } catch (e: Throwable) { - proc.destroy() + proc.destroyForcibly() throw e } } @@ -28,6 +29,6 @@ fun assertAliveAndKill(proc: Process) { try { assertEquals(proc.isAlive, true) } finally { - proc.destroy() + proc.destroyForcibly() } } \ No newline at end of file diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt index e206bb25bb..f2739046dc 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt @@ -1,5 +1,7 @@ package com.r3corda.core.testing.utilities +import com.google.common.net.HostAndPort +import java.io.IOException import java.io.InputStreamReader import java.net.ConnectException import java.net.SocketException @@ -11,22 +13,23 @@ class NodeDidNotStartException: Throwable { constructor(message: String): super(message) {} } -fun ensureNodeStartsOrKill(proc: Process, nodeAddr: String) { +fun ensureNodeStartsOrKill(proc: Process, nodeAddr: HostAndPort) { try { assertEquals(proc.isAlive, true) waitForNodeStartup(nodeAddr) } catch (e: Exception) { - proc.destroy() + println("Forcibly killing node process") + proc.destroyForcibly() throw e } } -private fun waitForNodeStartup(nodeAddr: String) { +private fun waitForNodeStartup(nodeAddr: HostAndPort) { + val url = URL("http://${nodeAddr.hostText}:${nodeAddr.port + 1}/api/status") var retries = 0 var respCode: Int do { retries++ - val url = URL(nodeAddr + "/api/status") val err = try { val conn = url.openConnection() as HttpURLConnection conn.requestMethod = "GET" @@ -39,9 +42,12 @@ private fun waitForNodeStartup(nodeAddr: String) { } catch(e: SocketException) { respCode = -1 "Could not connect: ${e.toString()}" + } catch (e: IOException) { + respCode = -1 + "IOException: ${e.toString()}" } - if(retries > 200) { + if(retries > 50) { throw NodeDidNotStartException("The node did not start: " + err) } } while (respCode != 200) From 4900c7eb2688ae518b51b1570b7e4270bddd4a74 Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Tue, 21 Jun 2016 21:32:38 +0100 Subject: [PATCH 40/48] Ports now randomised during demo tests. --- .../com/r3corda/core/testing/IRSDemoTest.kt | 20 +++++++++------- .../r3corda/core/testing/TraderDemoTest.kt | 24 ++++++++++++------- .../core/testing/utilities/JVMSpawner.kt | 4 ++-- .../r3corda/core/testing/utilities/NodeApi.kt | 6 ++--- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 16 ++++++------- 5 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index 229d74e406..1dc4220e80 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -18,8 +18,8 @@ class IRSDemoTest { try { setupNode(dirA, "NodeA") setupNode(dirB, "NodeB") - procA = startNode(dirA, "NodeA", nodeAddrA) - procB = startNode(dirB, "NodeB", freeLocalHostAndPort()) + procA = startNode(dirA, "NodeA", nodeAddrA, nodeAddrA) + procB = startNode(dirB, "NodeB", freeLocalHostAndPort(), nodeAddrA) runTrade(apiAddrA) runDateChange(apiAddrA) } finally { @@ -33,16 +33,20 @@ class IRSDemoTest { private fun setupNode(dir: Path, nodeType: String) { println("Running setup for $nodeType") val args = listOf("--role", "Setup" + nodeType, "--dir", dir.toString()) - val proc = spawn("com.r3corda.demos.IRSDemoKt", args) + val proc = spawn("com.r3corda.demos.IRSDemoKt", args, "IRSDemoSetup$nodeType") assertExitOrKill(proc) assertEquals(proc.exitValue(), 0) } -private fun startNode(dir: Path, nodeType: String, nodeAddr: HostAndPort): Process { +private fun startNode(dir: Path, nodeType: String, nodeAddr: HostAndPort, networkMapAddr: HostAndPort): Process { println("Running node $nodeType") println("Node addr: ${nodeAddr.toString()}") - val args = listOf("--role", nodeType, "--dir", dir.toString(), "--network-address", nodeAddr.toString()) - val proc = spawn("com.r3corda.demos.IRSDemoKt", args) + val args = listOf( + "--role", nodeType, + "--dir", dir.toString(), + "--network-address", nodeAddr.toString(), + "--network-map-address", networkMapAddr.toString()) + val proc = spawn("com.r3corda.demos.IRSDemoKt", args, "IRSDemo$nodeType") ensureNodeStartsOrKill(proc, nodeAddr) return proc } @@ -50,7 +54,7 @@ private fun startNode(dir: Path, nodeType: String, nodeAddr: HostAndPort): Proce private fun runTrade(nodeAddr: HostAndPort) { println("Running trade") val args = listOf("--role", "Trade", "trade1", "--network-address", "http://" + nodeAddr.toString()) - val proc = spawn("com.r3corda.demos.IRSDemoKt", args) + val proc = spawn("com.r3corda.demos.IRSDemoKt", args, "IRSDemoTrade") assertExitOrKill(proc) assertEquals(proc.exitValue(), 0) } @@ -58,7 +62,7 @@ private fun runTrade(nodeAddr: HostAndPort) { private fun runDateChange(nodeAddr: HostAndPort) { println("Running date change") val args = listOf("--role", "Date", "2017-01-02", "--network-address", "http://" + nodeAddr.toString()) - val proc = spawn("com.r3corda.demos.IRSDemoKt", args) + val proc = spawn("com.r3corda.demos.IRSDemoKt", args, "IRSDemoDate") assertExitOrKill(proc) assertEquals(proc.exitValue(), 0) } diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt index 62ab740729..0dcb4c0793 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt @@ -1,5 +1,6 @@ package com.r3corda.core.testing +import com.google.common.net.HostAndPort import com.r3corda.core.testing.utilities.assertExitOrKill import com.r3corda.core.testing.utilities.ensureNodeStartsOrKill import com.r3corda.core.testing.utilities.spawn @@ -9,11 +10,12 @@ import kotlin.test.assertEquals class TraderDemoTest { @Test fun `runs trader demo`() { + val buyerAddr = freeLocalHostAndPort() var nodeProc: Process? = null try { cleanupFiles() - nodeProc = runBuyer() - runSeller() + nodeProc = runBuyer(buyerAddr) + runSeller(buyerAddr) } finally { nodeProc?.destroy() cleanupFiles() @@ -21,18 +23,22 @@ class TraderDemoTest { } } -private fun runBuyer(): Process { +private fun runBuyer(buyerAddr: HostAndPort): Process { println("Running Buyer") - val args = listOf("--role", "BUYER") - val proc = spawn("com.r3corda.demos.TraderDemoKt", args) - ensureNodeStartsOrKill(proc, freeLocalHostAndPort()) + val args = listOf("--role", "BUYER", "--network-address", buyerAddr.toString()) + val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoBuyer") + ensureNodeStartsOrKill(proc, buyerAddr) return proc } -private fun runSeller() { +private fun runSeller(buyerAddr: HostAndPort) { println("Running Seller") - val args = listOf("--role", "SELLER") - val proc = spawn("com.r3corda.demos.TraderDemoKt", args) + val sellerAddr = freeLocalHostAndPort() + val args = listOf( + "--role", "SELLER", + "--network-address", sellerAddr.toString(), + "--other-network-address", buyerAddr.toString()) + val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoSeller") assertExitOrKill(proc); assertEquals(proc.exitValue(), 0) } diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt index bfc7b3f8f2..5d7a063c1b 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt @@ -4,11 +4,11 @@ import java.nio.file.Paths import java.util.concurrent.TimeUnit import kotlin.test.assertEquals -fun spawn(className: String, args: List): Process { +fun spawn(className: String, args: List, appName: String): Process { val separator = System.getProperty("file.separator") val classpath = System.getProperty("java.class.path") val path = System.getProperty("java.home") + separator + "bin" + separator + "java" - val javaArgs = listOf(path, "-javaagent:lib/quasar.jar", "-cp", classpath, className) + val javaArgs = listOf(path, "-Dname=$appName", "-javaagent:lib/quasar.jar", "-cp", classpath, className) val builder = ProcessBuilder(javaArgs + args) builder.redirectError(Paths.get("error.$className.log").toFile()) builder.inheritIO() diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt index f2739046dc..f589da19a4 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt @@ -9,7 +9,7 @@ import java.net.HttpURLConnection import java.net.URL import kotlin.test.assertEquals -class NodeDidNotStartException: Throwable { +class NodeDidNotStartException: Exception { constructor(message: String): super(message) {} } @@ -17,7 +17,7 @@ fun ensureNodeStartsOrKill(proc: Process, nodeAddr: HostAndPort) { try { assertEquals(proc.isAlive, true) waitForNodeStartup(nodeAddr) - } catch (e: Exception) { + } catch (e: Throwable) { println("Forcibly killing node process") proc.destroyForcibly() throw e @@ -47,7 +47,7 @@ private fun waitForNodeStartup(nodeAddr: HostAndPort) { "IOException: ${e.toString()}" } - if(retries > 50) { + if(retries > 25) { throw NodeDidNotStartException("The node did not start: " + err) } } while (respCode != 200) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index af485386df..e8e983d166 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -61,7 +61,7 @@ enum class IRSDemoRole { private class NodeParams() { var dir : Path = Paths.get("") - var address : String = "" + var address : HostAndPort = HostAndPort.fromString("localhost").withDefaultPort(Node.DEFAULT_PORT) var mapAddress: String = "" var identityFile: Path = Paths.get("") var tradeWithAddrs: List = listOf() @@ -235,7 +235,7 @@ private fun configureNodeParams(role: IRSDemoRole, args: DemoArgs, options: Opti nodeParams.dir = Paths.get(options.valueOf(args.dirArg)) } if (options.has(args.networkAddressArg)) { - nodeParams.address = options.valueOf(args.networkAddressArg) + nodeParams.address = HostAndPort.fromString(options.valueOf(args.networkAddressArg)).withDefaultPort(Node.DEFAULT_PORT) } nodeParams.identityFile = if (options.has(args.networkMapIdentityFile)) { Paths.get(options.valueOf(args.networkMapIdentityFile)) @@ -265,7 +265,8 @@ private fun runNode(nodeParams: NodeParams) : Unit { ExitServerProtocol.Handler.register(node) if(nodeParams.uploadRates) { - runUploadRates("http://localhost:31341") + val apiAddr = "http://${nodeParams.address.hostText}:${nodeParams.address.port + 1}"; + runUploadRates(apiAddr) } try { @@ -283,8 +284,7 @@ private fun createRecipient(addr: String) : SingleMessageRecipient { private fun startNode(params : NodeParams, networkMap: SingleMessageRecipient, recipients: List) : Node { val config = getNodeConfig(params) val advertisedServices: Set - val myNetAddr = HostAndPort.fromString(params.address).withDefaultPort(Node.DEFAULT_PORT) - val networkMapId = if (params.mapAddress.equals(params.address)) { + val networkMapId = if (params.mapAddress.equals(params.address.toString())) { // This node provides network map and notary services advertisedServices = setOf(NetworkMapService.Type, SimpleNotaryService.Type) null @@ -293,7 +293,7 @@ private fun startNode(params : NodeParams, networkMap: SingleMessageRecipient, r nodeInfo(networkMap, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type)) } - val node = logElapsedTime("Node startup") { Node(params.dir, myNetAddr, config, networkMapId, + val node = logElapsedTime("Node startup") { Node(params.dir, params.address, config, networkMapId, advertisedServices, DemoClock(), listOf(InterestRateSwapAPI::class.java)).start() } @@ -415,7 +415,7 @@ private fun uploadFile(url: URL, file: String) : Boolean { private fun createNodeAParams() : NodeParams { val params = NodeParams() params.dir = Paths.get("nodeA") - params.address = "localhost" + params.address = HostAndPort.fromString("localhost") params.tradeWithAddrs = listOf("localhost:31340") params.tradeWithIdentities = listOf(getRoleDir(IRSDemoRole.NodeB).resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME)) params.defaultLegalName = "Bank A" @@ -425,7 +425,7 @@ private fun createNodeAParams() : NodeParams { private fun createNodeBParams() : NodeParams { val params = NodeParams() params.dir = Paths.get("nodeB") - params.address = "localhost:31340" + params.address = HostAndPort.fromString("localhost:31340") params.tradeWithAddrs = listOf("localhost") params.tradeWithIdentities = listOf(getRoleDir(IRSDemoRole.NodeA).resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME)) params.defaultLegalName = "Bank B" From ffa9ad1bc94c67dfddb572121bb742aace10956e Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Wed, 22 Jun 2016 16:21:44 +0100 Subject: [PATCH 41/48] Added port argument for IRS demo to allow web servers to not have binding collisions during testing and to allow more granular control over demos. --- .../kotlin/com/r3corda/node/internal/Node.kt | 12 +++++++--- .../com/r3corda/core/testing/IRSDemoTest.kt | 18 ++++++++++----- .../r3corda/core/testing/utilities/NodeApi.kt | 2 +- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 22 ++++++++++++++----- 4 files changed, 38 insertions(+), 16 deletions(-) diff --git a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt index 6c3ddbdda4..e23436acbf 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt @@ -71,6 +71,9 @@ class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration // when our process shuts down, but we try in stop() anyway just to be nice. private var nodeFileLock: FileLock? = null + // Todo: Move to node config file + private var webServerPort: Int = p2pAddr.port + 1 + override fun makeMessagingService(): MessagingService = ArtemisMessagingService(dir, p2pAddr, serverThread) override fun startMessagingService() { @@ -78,11 +81,14 @@ class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration (net as ArtemisMessagingService).start() } + fun setWebServerPort(port: Int): Node { + webServerPort = port + return this + } + private fun initWebServer(): Server { // Note that the web server handlers will all run concurrently, and not on the node thread. - - val port = p2pAddr.port + 1 // TODO: Move this into the node config file. - val server = Server(port) + val server = Server(webServerPort) val handlerCollection = HandlerCollection() diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index 1dc4220e80..57810c625d 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -10,7 +10,8 @@ import java.nio.file.Paths class IRSDemoTest { @Test fun `runs IRS demo`() { val nodeAddrA = freeLocalHostAndPort() - val apiAddrA = HostAndPort.fromString("${nodeAddrA.hostText}:${nodeAddrA.port + 1}") + val apiAddrA = freeLocalHostAndPort() + val apiAddrB = freeLocalHostAndPort() val dirA = Paths.get("./nodeA") val dirB = Paths.get("./nodeB") var procA: Process? = null @@ -18,8 +19,8 @@ class IRSDemoTest { try { setupNode(dirA, "NodeA") setupNode(dirB, "NodeB") - procA = startNode(dirA, "NodeA", nodeAddrA, nodeAddrA) - procB = startNode(dirB, "NodeB", freeLocalHostAndPort(), nodeAddrA) + procA = startNode(dirA, "NodeA", nodeAddrA, nodeAddrA, apiAddrA) + procB = startNode(dirB, "NodeB", freeLocalHostAndPort(), nodeAddrA, apiAddrB) runTrade(apiAddrA) runDateChange(apiAddrA) } finally { @@ -38,16 +39,21 @@ private fun setupNode(dir: Path, nodeType: String) { assertEquals(proc.exitValue(), 0) } -private fun startNode(dir: Path, nodeType: String, nodeAddr: HostAndPort, networkMapAddr: HostAndPort): Process { +private fun startNode(dir: Path, + nodeType: String, + nodeAddr: HostAndPort, + networkMapAddr: HostAndPort, + apiAddr: HostAndPort): Process { println("Running node $nodeType") println("Node addr: ${nodeAddr.toString()}") val args = listOf( "--role", nodeType, "--dir", dir.toString(), "--network-address", nodeAddr.toString(), - "--network-map-address", networkMapAddr.toString()) + "--network-map-address", networkMapAddr.toString(), + "--api-address", apiAddr.port.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args, "IRSDemo$nodeType") - ensureNodeStartsOrKill(proc, nodeAddr) + ensureNodeStartsOrKill(proc, apiAddr) return proc } diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt index f589da19a4..536acaf758 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt @@ -25,7 +25,7 @@ fun ensureNodeStartsOrKill(proc: Process, nodeAddr: HostAndPort) { } private fun waitForNodeStartup(nodeAddr: HostAndPort) { - val url = URL("http://${nodeAddr.hostText}:${nodeAddr.port + 1}/api/status") + val url = URL("http://${nodeAddr.toString()}/api/status") var retries = 0 var respCode: Int do { diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index e8e983d166..84f37276ac 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -63,6 +63,7 @@ private class NodeParams() { var dir : Path = Paths.get("") var address : HostAndPort = HostAndPort.fromString("localhost").withDefaultPort(Node.DEFAULT_PORT) var mapAddress: String = "" + var apiPort : Int = Node.DEFAULT_PORT + 1 var identityFile: Path = Paths.get("") var tradeWithAddrs: List = listOf() var tradeWithIdentities: List = listOf() @@ -73,6 +74,7 @@ private class NodeParams() { private class DemoArgs() { lateinit var roleArg: OptionSpec lateinit var networkAddressArg: OptionSpec + lateinit var apiPort: OptionSpec lateinit var dirArg: OptionSpec lateinit var networkMapIdentityFile: OptionSpec lateinit var networkMapNetAddr: OptionSpec @@ -179,6 +181,7 @@ private fun setupArgs(parser: OptionParser): DemoArgs { args.roleArg = parser.accepts("role").withRequiredArg().ofType(IRSDemoRole::class.java).required() args.networkAddressArg = parser.accepts("network-address").withOptionalArg() + args.apiPort = parser.accepts("api-address").withOptionalArg().ofType(Int::class.java) args.dirArg = parser.accepts("directory").withOptionalArg() args.networkMapIdentityFile = parser.accepts("network-map-identity-file").withOptionalArg() args.networkMapNetAddr = parser.accepts("network-map-address").withRequiredArg().defaultsTo("localhost") @@ -237,6 +240,12 @@ private fun configureNodeParams(role: IRSDemoRole, args: DemoArgs, options: Opti if (options.has(args.networkAddressArg)) { nodeParams.address = HostAndPort.fromString(options.valueOf(args.networkAddressArg)).withDefaultPort(Node.DEFAULT_PORT) } + if(options.has(args.apiPort)) { + nodeParams.apiPort = options.valueOf(args.apiPort) + } else if(options.has(args.networkAddressArg)) { + nodeParams.apiPort = nodeParams.address.port + 1 + } + nodeParams.identityFile = if (options.has(args.networkMapIdentityFile)) { Paths.get(options.valueOf(args.networkMapIdentityFile)) } else { @@ -265,8 +274,7 @@ private fun runNode(nodeParams: NodeParams) : Unit { ExitServerProtocol.Handler.register(node) if(nodeParams.uploadRates) { - val apiAddr = "http://${nodeParams.address.hostText}:${nodeParams.address.port + 1}"; - runUploadRates(apiAddr) + runUploadRates(HostAndPort.fromString("localhost:${nodeParams.apiPort}")) } try { @@ -295,7 +303,7 @@ private fun startNode(params : NodeParams, networkMap: SingleMessageRecipient, r val node = logElapsedTime("Node startup") { Node(params.dir, params.address, config, networkMapId, advertisedServices, DemoClock(), - listOf(InterestRateSwapAPI::class.java)).start() } + listOf(InterestRateSwapAPI::class.java)).setWebServerPort(params.apiPort).start() } // TODO: This should all be replaced by the identity service being updated // as the network map changes. @@ -321,12 +329,12 @@ private fun nodeInfo(recipient: SingleMessageRecipient, identityFile: Path, adve } } -private fun runUploadRates(host: String) { +private fun runUploadRates(host: HostAndPort) { val fileContents = IOUtils.toString(NodeParams::class.java.getResource("example.rates.txt")) var timer : Timer? = null timer = fixedRateTimer("upload-rates", false, 0, 5000, { try { - val url = URL(host + "/upload/interest-rates") + val url = URL("http://${host.toString()}/upload/interest-rates") if(uploadFile(url, fileContents)) { timer!!.cancel() println("Rates uploaded successfully") @@ -415,7 +423,8 @@ private fun uploadFile(url: URL, file: String) : Boolean { private fun createNodeAParams() : NodeParams { val params = NodeParams() params.dir = Paths.get("nodeA") - params.address = HostAndPort.fromString("localhost") + params.address = HostAndPort.fromString("localhost").withDefaultPort(Node.DEFAULT_PORT) + params.apiPort = Node.DEFAULT_PORT + 1 params.tradeWithAddrs = listOf("localhost:31340") params.tradeWithIdentities = listOf(getRoleDir(IRSDemoRole.NodeB).resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME)) params.defaultLegalName = "Bank A" @@ -426,6 +435,7 @@ private fun createNodeBParams() : NodeParams { val params = NodeParams() params.dir = Paths.get("nodeB") params.address = HostAndPort.fromString("localhost:31340") + params.apiPort = 31341 params.tradeWithAddrs = listOf("localhost") params.tradeWithIdentities = listOf(getRoleDir(IRSDemoRole.NodeA).resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME)) params.defaultLegalName = "Bank B" From 429d8aab74dad9e938967ff9e8fa564b942aaa17 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Mon, 27 Jun 2016 10:23:48 +0100 Subject: [PATCH 42/48] node, integtest: Code style --- .../r3corda/node/internal/APIServerImpl.kt | 2 +- .../kotlin/com/r3corda/node/internal/Node.kt | 2 +- .../node/servlets/DataUploadServlet.kt | 3 +-- .../com/r3corda/core/testing/IRSDemoTest.kt | 5 ++-- .../r3corda/core/testing/utilities/NodeApi.kt | 4 +-- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 26 +++++++++---------- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/node/src/main/kotlin/com/r3corda/node/internal/APIServerImpl.kt b/node/src/main/kotlin/com/r3corda/node/internal/APIServerImpl.kt index 6f4eae6e6c..85cafc6692 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/APIServerImpl.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/APIServerImpl.kt @@ -19,7 +19,7 @@ class APIServerImpl(val node: AbstractNode) : APIServer { override fun serverTime(): LocalDateTime = LocalDateTime.now(node.services.clock) override fun status(): Response { - return if(node.started) { + return if (node.started) { Response.ok("started").build() } else { Response.status(Response.Status.SERVICE_UNAVAILABLE).entity("not started").build() diff --git a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt index e23436acbf..3c444317c2 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt @@ -71,7 +71,7 @@ class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration // when our process shuts down, but we try in stop() anyway just to be nice. private var nodeFileLock: FileLock? = null - // Todo: Move to node config file + // TODO: Move to node config file private var webServerPort: Int = p2pAddr.port + 1 override fun makeMessagingService(): MessagingService = ArtemisMessagingService(dir, p2pAddr, serverThread) diff --git a/node/src/main/kotlin/com/r3corda/node/servlets/DataUploadServlet.kt b/node/src/main/kotlin/com/r3corda/node/servlets/DataUploadServlet.kt index 6a0944dd16..742061b22d 100644 --- a/node/src/main/kotlin/com/r3corda/node/servlets/DataUploadServlet.kt +++ b/node/src/main/kotlin/com/r3corda/node/servlets/DataUploadServlet.kt @@ -36,8 +36,7 @@ class DataUploadServlet : HttpServlet() { val iterator = upload.getItemIterator(req) val messages = ArrayList() - if(!iterator.hasNext()) - { + if (!iterator.hasNext()) { resp.sendError(HttpServletResponse.SC_BAD_REQUEST, "Got an upload request with no files") return } diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index 57810c625d..9b7dbbd530 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -31,6 +31,7 @@ class IRSDemoTest { } } } + private fun setupNode(dir: Path, nodeType: String) { println("Running setup for $nodeType") val args = listOf("--role", "Setup" + nodeType, "--dir", dir.toString()) @@ -74,7 +75,7 @@ private fun runDateChange(nodeAddr: HostAndPort) { } private fun stopNode(nodeProc: Process?) { - if(nodeProc != null) { + if (nodeProc != null) { println("Stopping node") assertAliveAndKill(nodeProc) } @@ -83,4 +84,4 @@ private fun stopNode(nodeProc: Process?) { private fun cleanup(dir: Path) { println("Erasing: " + dir.toString()) dir.toFile().deleteRecursively() -} \ No newline at end of file +} diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt index 536acaf758..0f6d6239e7 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt @@ -47,8 +47,8 @@ private fun waitForNodeStartup(nodeAddr: HostAndPort) { "IOException: ${e.toString()}" } - if(retries > 25) { + if (retries > 25) { throw NodeDidNotStartException("The node did not start: " + err) } } while (respCode != 200) -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 84f37276ac..091837bf01 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -147,7 +147,7 @@ private fun runDateChange(demoArgs: DemoArgs, options: OptionSet): Int { "http://localhost:" + (Node.DEFAULT_PORT + 1) } - if(!changeDate(dateStr, host)) { + if (!changeDate(dateStr, host)) { return 1 } } else { @@ -161,7 +161,7 @@ private fun runDateChange(demoArgs: DemoArgs, options: OptionSet): Int { private fun runNode(role: IRSDemoRole, demoArgs: DemoArgs, options: OptionSet): Int { // If these directory and identity file arguments aren't specified then we can assume a default setup and // create everything that is needed without needing to run setup. - if(!options.has(demoArgs.dirArg) && !options.has(demoArgs.fakeTradeWithIdentityFile)) { + if (!options.has(demoArgs.dirArg) && !options.has(demoArgs.fakeTradeWithIdentityFile)) { createNodeConfig(createNodeAParams()); createNodeConfig(createNodeBParams()); } @@ -201,7 +201,7 @@ private fun setup(params: NodeParams): Int { private fun changeDate(date: String, host: String) : Boolean { println("Changing date to " + date) val url = URL(host + "/api/irs/demodate") - if(putJson(url, "\"" + date + "\"")) { + if (putJson(url, "\"" + date + "\"")) { println("Date changed") return true } else { @@ -215,7 +215,7 @@ private fun uploadTrade(tradeId: String, host: String) : Boolean { val fileContents = IOUtils.toString(NodeParams::class.java.getResourceAsStream("example-irs-trade.json")) val tradeFile = fileContents.replace("tradeXXX", tradeId) val url = URL(host + "/api/irs/deals") - if(postJson(url, tradeFile)) { + if (postJson(url, tradeFile)) { println("Trade sent") return true } else { @@ -240,9 +240,9 @@ private fun configureNodeParams(role: IRSDemoRole, args: DemoArgs, options: Opti if (options.has(args.networkAddressArg)) { nodeParams.address = HostAndPort.fromString(options.valueOf(args.networkAddressArg)).withDefaultPort(Node.DEFAULT_PORT) } - if(options.has(args.apiPort)) { + if (options.has(args.apiPort)) { nodeParams.apiPort = options.valueOf(args.apiPort) - } else if(options.has(args.networkAddressArg)) { + } else if (options.has(args.networkAddressArg)) { nodeParams.apiPort = nodeParams.address.port + 1 } @@ -261,7 +261,7 @@ private fun configureNodeParams(role: IRSDemoRole, args: DemoArgs, options: Opti return nodeParams } -private fun runNode(nodeParams: NodeParams) : Unit { +private fun runNode(nodeParams: NodeParams): Unit { val networkMap = createRecipient(nodeParams.mapAddress) val destinations = nodeParams.tradeWithAddrs.map({ createRecipient(it) @@ -273,7 +273,7 @@ private fun runNode(nodeParams: NodeParams) : Unit { UpdateBusinessDayProtocol.Handler.register(node) ExitServerProtocol.Handler.register(node) - if(nodeParams.uploadRates) { + if (nodeParams.uploadRates) { runUploadRates(HostAndPort.fromString("localhost:${nodeParams.apiPort}")) } @@ -335,7 +335,7 @@ private fun runUploadRates(host: HostAndPort) { timer = fixedRateTimer("upload-rates", false, 0, 5000, { try { val url = URL("http://${host.toString()}/upload/interest-rates") - if(uploadFile(url, fileContents)) { + if (uploadFile(url, fileContents)) { timer!!.cancel() println("Rates uploaded successfully") } else { @@ -450,7 +450,7 @@ private fun createNodeConfig(params: NodeParams) : NodeConfiguration { val configFile = params.dir.resolve("config").toFile() val config = loadConfigFile(configFile, params.defaultLegalName) - if(!Files.exists(params.dir.resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME))) { + if (!Files.exists(params.dir.resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME))) { createIdentities(params, config) } @@ -458,11 +458,11 @@ private fun createNodeConfig(params: NodeParams) : NodeConfiguration { } private fun getNodeConfig(params: NodeParams): NodeConfiguration { - if(!Files.exists(params.dir)) { + if (!Files.exists(params.dir)) { throw NotSetupException("Missing config directory. Please run node setup before running the node") } - if(!Files.exists(params.dir.resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME))) { + if (!Files.exists(params.dir.resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME))) { throw NotSetupException("Missing identity file. Please run node setup before running the node") } @@ -471,7 +471,7 @@ private fun getNodeConfig(params: NodeParams): NodeConfiguration { } private fun getRoleDir(role: IRSDemoRole) : Path { - when(role) { + when (role) { IRSDemoRole.NodeA -> return Paths.get("nodeA") IRSDemoRole.NodeB -> return Paths.get("nodeB") else -> { From e54dad9a8b90643c366f8ce26eb0e0f79327b0c6 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Mon, 27 Jun 2016 14:10:30 +0100 Subject: [PATCH 43/48] node: Add apiAddress constructor parameter --- .../kotlin/com/r3corda/node/internal/Node.kt | 13 ++------- .../com/r3corda/core/testing/IRSDemoTest.kt | 2 +- .../r3corda/core/testing/TraderDemoTest.kt | 18 ++++++++---- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 29 ++++++++----------- .../kotlin/com/r3corda/demos/RateFixDemo.kt | 7 +++-- .../kotlin/com/r3corda/demos/TraderDemo.kt | 4 ++- 6 files changed, 37 insertions(+), 36 deletions(-) diff --git a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt index 3c444317c2..85cfac29a3 100644 --- a/node/src/main/kotlin/com/r3corda/node/internal/Node.kt +++ b/node/src/main/kotlin/com/r3corda/node/internal/Node.kt @@ -25,6 +25,7 @@ import org.glassfish.jersey.server.ServerProperties import org.glassfish.jersey.servlet.ServletContainer import java.io.RandomAccessFile import java.lang.management.ManagementFactory +import java.net.InetSocketAddress import java.nio.channels.FileLock import java.nio.file.Files import java.nio.file.Path @@ -52,7 +53,7 @@ class ConfigurationException(message: String) : Exception(message) * Listed clientAPI classes are assumed to have to take a single APIServer constructor parameter * @param clock The clock used within the node and by all protocols etc */ -class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration, +class Node(dir: Path, val p2pAddr: HostAndPort, val webServerAddr: HostAndPort, configuration: NodeConfiguration, networkMapAddress: NodeInfo?, advertisedServices: Set, clock: Clock = NodeClock(), val clientAPIs: List> = listOf()) : AbstractNode(dir, configuration, networkMapAddress, advertisedServices, clock) { @@ -71,9 +72,6 @@ class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration // when our process shuts down, but we try in stop() anyway just to be nice. private var nodeFileLock: FileLock? = null - // TODO: Move to node config file - private var webServerPort: Int = p2pAddr.port + 1 - override fun makeMessagingService(): MessagingService = ArtemisMessagingService(dir, p2pAddr, serverThread) override fun startMessagingService() { @@ -81,14 +79,9 @@ class Node(dir: Path, val p2pAddr: HostAndPort, configuration: NodeConfiguration (net as ArtemisMessagingService).start() } - fun setWebServerPort(port: Int): Node { - webServerPort = port - return this - } - private fun initWebServer(): Server { // Note that the web server handlers will all run concurrently, and not on the node thread. - val server = Server(webServerPort) + val server = Server(InetSocketAddress(webServerAddr.hostText, webServerAddr.port)) val handlerCollection = HandlerCollection() diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index 9b7dbbd530..d8f4016078 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -52,7 +52,7 @@ private fun startNode(dir: Path, "--dir", dir.toString(), "--network-address", nodeAddr.toString(), "--network-map-address", networkMapAddr.toString(), - "--api-address", apiAddr.port.toString()) + "--api-address", apiAddr.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args, "IRSDemo$nodeType") ensureNodeStartsOrKill(proc, apiAddr) return proc diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt index 0dcb4c0793..7c1cd52ec2 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt @@ -11,10 +11,11 @@ import kotlin.test.assertEquals class TraderDemoTest { @Test fun `runs trader demo`() { val buyerAddr = freeLocalHostAndPort() + val buyerApiAddr = freeLocalHostAndPort() var nodeProc: Process? = null try { cleanupFiles() - nodeProc = runBuyer(buyerAddr) + nodeProc = runBuyer(buyerAddr, buyerApiAddr) runSeller(buyerAddr) } finally { nodeProc?.destroy() @@ -23,9 +24,13 @@ class TraderDemoTest { } } -private fun runBuyer(buyerAddr: HostAndPort): Process { +private fun runBuyer(buyerAddr: HostAndPort, buyerApiAddr: HostAndPort): Process { println("Running Buyer") - val args = listOf("--role", "BUYER", "--network-address", buyerAddr.toString()) + val args = listOf( + "--role", "BUYER", + "--network-address", buyerAddr.toString(), + "--api-address", buyerApiAddr.toString() + ) val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoBuyer") ensureNodeStartsOrKill(proc, buyerAddr) return proc @@ -34,10 +39,13 @@ private fun runBuyer(buyerAddr: HostAndPort): Process { private fun runSeller(buyerAddr: HostAndPort) { println("Running Seller") val sellerAddr = freeLocalHostAndPort() + val sellerApiAddr = freeLocalHostAndPort() val args = listOf( "--role", "SELLER", "--network-address", sellerAddr.toString(), - "--other-network-address", buyerAddr.toString()) + "--api-address", sellerApiAddr.toString(), + "--other-network-address", buyerAddr.toString() + ) val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoSeller") assertExitOrKill(proc); assertEquals(proc.exitValue(), 0) @@ -48,4 +56,4 @@ private fun cleanupFiles() { val dir = Paths.get("trader-demo") println("Erasing " + dir) dir.toFile().deleteRecursively() -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 091837bf01..48022c022a 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -4,7 +4,6 @@ import com.google.common.net.HostAndPort import com.typesafe.config.ConfigFactory import com.r3corda.core.crypto.Party import com.r3corda.core.logElapsedTime -import com.r3corda.core.messaging.MessageRecipients import com.r3corda.core.messaging.SingleMessageRecipient import com.r3corda.node.internal.Node import com.r3corda.node.services.config.NodeConfiguration @@ -12,7 +11,6 @@ import com.r3corda.node.services.config.NodeConfigurationFromConfig import com.r3corda.core.node.NodeInfo import com.r3corda.node.services.network.NetworkMapService import com.r3corda.node.services.clientapi.NodeInterestRates -import com.r3corda.node.services.transactions.NotaryService import com.r3corda.core.node.services.ServiceType import com.r3corda.node.services.messaging.ArtemisMessagingService import com.r3corda.core.serialization.deserialize @@ -23,12 +21,10 @@ import com.r3corda.demos.protocols.ExitServerProtocol import com.r3corda.demos.protocols.UpdateBusinessDayProtocol import com.r3corda.node.internal.AbstractNode import com.r3corda.node.internal.testing.MockNetwork -import com.r3corda.node.services.network.InMemoryMessagingNetwork import com.r3corda.node.services.transactions.SimpleNotaryService import joptsimple.OptionParser import joptsimple.OptionSet import joptsimple.OptionSpec -import joptsimple.OptionSpecBuilder import java.io.DataOutputStream import java.io.File import java.net.HttpURLConnection @@ -63,7 +59,7 @@ private class NodeParams() { var dir : Path = Paths.get("") var address : HostAndPort = HostAndPort.fromString("localhost").withDefaultPort(Node.DEFAULT_PORT) var mapAddress: String = "" - var apiPort : Int = Node.DEFAULT_PORT + 1 + var apiAddress: HostAndPort = HostAndPort.fromString("localhost").withDefaultPort(Node.DEFAULT_PORT + 1) var identityFile: Path = Paths.get("") var tradeWithAddrs: List = listOf() var tradeWithIdentities: List = listOf() @@ -74,7 +70,7 @@ private class NodeParams() { private class DemoArgs() { lateinit var roleArg: OptionSpec lateinit var networkAddressArg: OptionSpec - lateinit var apiPort: OptionSpec + lateinit var apiAddressArg: OptionSpec lateinit var dirArg: OptionSpec lateinit var networkMapIdentityFile: OptionSpec lateinit var networkMapNetAddr: OptionSpec @@ -181,7 +177,7 @@ private fun setupArgs(parser: OptionParser): DemoArgs { args.roleArg = parser.accepts("role").withRequiredArg().ofType(IRSDemoRole::class.java).required() args.networkAddressArg = parser.accepts("network-address").withOptionalArg() - args.apiPort = parser.accepts("api-address").withOptionalArg().ofType(Int::class.java) + args.apiAddressArg = parser.accepts("api-address").withOptionalArg() args.dirArg = parser.accepts("directory").withOptionalArg() args.networkMapIdentityFile = parser.accepts("network-map-identity-file").withOptionalArg() args.networkMapNetAddr = parser.accepts("network-map-address").withRequiredArg().defaultsTo("localhost") @@ -240,10 +236,8 @@ private fun configureNodeParams(role: IRSDemoRole, args: DemoArgs, options: Opti if (options.has(args.networkAddressArg)) { nodeParams.address = HostAndPort.fromString(options.valueOf(args.networkAddressArg)).withDefaultPort(Node.DEFAULT_PORT) } - if (options.has(args.apiPort)) { - nodeParams.apiPort = options.valueOf(args.apiPort) - } else if (options.has(args.networkAddressArg)) { - nodeParams.apiPort = nodeParams.address.port + 1 + if (options.has(args.apiAddressArg)) { + nodeParams.apiAddress = HostAndPort.fromString(options.valueOf(args.apiAddressArg)).withDefaultPort(Node.DEFAULT_PORT + 1) } nodeParams.identityFile = if (options.has(args.networkMapIdentityFile)) { @@ -274,7 +268,7 @@ private fun runNode(nodeParams: NodeParams): Unit { ExitServerProtocol.Handler.register(node) if (nodeParams.uploadRates) { - runUploadRates(HostAndPort.fromString("localhost:${nodeParams.apiPort}")) + runUploadRates(nodeParams.apiAddress) } try { @@ -301,9 +295,10 @@ private fun startNode(params : NodeParams, networkMap: SingleMessageRecipient, r nodeInfo(networkMap, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type)) } - val node = logElapsedTime("Node startup") { Node(params.dir, params.address, config, networkMapId, - advertisedServices, DemoClock(), - listOf(InterestRateSwapAPI::class.java)).setWebServerPort(params.apiPort).start() } + val node = logElapsedTime("Node startup") { + Node(params.dir, params.address, params.apiAddress, config, networkMapId, advertisedServices, DemoClock(), + listOf(InterestRateSwapAPI::class.java)).start() + } // TODO: This should all be replaced by the identity service being updated // as the network map changes. @@ -424,7 +419,7 @@ private fun createNodeAParams() : NodeParams { val params = NodeParams() params.dir = Paths.get("nodeA") params.address = HostAndPort.fromString("localhost").withDefaultPort(Node.DEFAULT_PORT) - params.apiPort = Node.DEFAULT_PORT + 1 + params.apiAddress = HostAndPort.fromString("localhost").withDefaultPort(Node.DEFAULT_PORT + 1) params.tradeWithAddrs = listOf("localhost:31340") params.tradeWithIdentities = listOf(getRoleDir(IRSDemoRole.NodeB).resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME)) params.defaultLegalName = "Bank A" @@ -435,7 +430,7 @@ private fun createNodeBParams() : NodeParams { val params = NodeParams() params.dir = Paths.get("nodeB") params.address = HostAndPort.fromString("localhost:31340") - params.apiPort = 31341 + params.apiAddress = HostAndPort.fromString("localhost:31341") params.tradeWithAddrs = listOf("localhost") params.tradeWithIdentities = listOf(getRoleDir(IRSDemoRole.NodeA).resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME)) params.defaultLegalName = "Bank B" diff --git a/src/main/kotlin/com/r3corda/demos/RateFixDemo.kt b/src/main/kotlin/com/r3corda/demos/RateFixDemo.kt index ee5f7fa4de..97bbbf2e4c 100644 --- a/src/main/kotlin/com/r3corda/demos/RateFixDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/RateFixDemo.kt @@ -1,5 +1,6 @@ package com.r3corda.demos +import com.google.common.net.HostAndPort import com.r3corda.contracts.cash.Cash import com.r3corda.core.contracts.* import com.r3corda.core.crypto.Party @@ -71,7 +72,9 @@ fun main(args: Array) { override val nearestCity: String = "Atlantis" } - val node = logElapsedTime("Node startup") { Node(dir, myNetAddr, config, networkMapAddress, + val apiAddr = HostAndPort.fromParts(myNetAddr.hostText, myNetAddr.port + 1) + + val node = logElapsedTime("Node startup") { Node(dir, myNetAddr, apiAddr, config, networkMapAddress, advertisedServices, DemoClock(), listOf(InterestRateSwapAPI::class.java)).setup().start() } @@ -89,4 +92,4 @@ fun main(args: Array) { println() print(Emoji.renderIfSupported(tx.toWireTransaction())) println(tx.toSignedTransaction().sigs) -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index ec93fe7780..83ecc5c418 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -77,6 +77,7 @@ fun runTraderDemo(args: Array): Int { val roleArg = parser.accepts("role").withRequiredArg().ofType(Role::class.java).required() val myNetworkAddress = parser.accepts("network-address").withRequiredArg().defaultsTo("localhost") val theirNetworkAddress = parser.accepts("other-network-address").withRequiredArg().defaultsTo("localhost") + val apiNetworkAddress = parser.accepts("api-address").withRequiredArg().defaultsTo("localhost") val options = try { parser.parse(*args) @@ -100,6 +101,7 @@ fun runTraderDemo(args: Array): Int { Role.SELLER -> 31337 } ) + val apiNetAddr = HostAndPort.fromString(options.valueOf(apiNetworkAddress)).withDefaultPort(myNetAddr.port + 1) // Suppress the Artemis MQ noise, and activate the demo logging. // @@ -143,7 +145,7 @@ fun runTraderDemo(args: Array): Int { // And now construct then start the node object. It takes a little while. val node = logElapsedTime("Node startup") { - Node(directory, myNetAddr, config, networkMapId, advertisedServices).setup().start() + Node(directory, myNetAddr, apiNetAddr, config, networkMapId, advertisedServices).setup().start() } // TODO: Replace with a separate trusted cash issuer From f2505fb5045f7695176e7f8405db927b9ebb07fb Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Mon, 27 Jun 2016 14:14:12 +0100 Subject: [PATCH 44/48] integtest: Put delay in status polling --- .../com/r3corda/core/testing/IRSDemoTest.kt | 2 +- .../r3corda/core/testing/TraderDemoTest.kt | 4 +- .../core/testing/utilities/JVMSpawner.kt | 2 +- .../r3corda/core/testing/utilities/NodeApi.kt | 87 ++++++++++--------- 4 files changed, 51 insertions(+), 44 deletions(-) diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index d8f4016078..a0e653525d 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -54,7 +54,7 @@ private fun startNode(dir: Path, "--network-map-address", networkMapAddr.toString(), "--api-address", apiAddr.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args, "IRSDemo$nodeType") - ensureNodeStartsOrKill(proc, apiAddr) + NodeApi.ensureNodeStartsOrKill(proc, apiAddr) return proc } diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt index 7c1cd52ec2..5a48ff5a28 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt @@ -1,8 +1,8 @@ package com.r3corda.core.testing import com.google.common.net.HostAndPort +import com.r3corda.core.testing.utilities.NodeApi import com.r3corda.core.testing.utilities.assertExitOrKill -import com.r3corda.core.testing.utilities.ensureNodeStartsOrKill import com.r3corda.core.testing.utilities.spawn import org.junit.Test import java.nio.file.Paths @@ -32,7 +32,7 @@ private fun runBuyer(buyerAddr: HostAndPort, buyerApiAddr: HostAndPort): Process "--api-address", buyerApiAddr.toString() ) val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoBuyer") - ensureNodeStartsOrKill(proc, buyerAddr) + NodeApi.ensureNodeStartsOrKill(proc, buyerApiAddr) return proc } diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt index 5d7a063c1b..2bd06445ca 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/JVMSpawner.kt @@ -31,4 +31,4 @@ fun assertAliveAndKill(proc: Process) { } finally { proc.destroyForcibly() } -} \ No newline at end of file +} diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt index 0f6d6239e7..867c1d26a5 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/NodeApi.kt @@ -9,46 +9,53 @@ import java.net.HttpURLConnection import java.net.URL import kotlin.test.assertEquals -class NodeDidNotStartException: Exception { - constructor(message: String): super(message) {} -} +class NodeApi { + class NodeDidNotStartException(message: String): Exception(message) -fun ensureNodeStartsOrKill(proc: Process, nodeAddr: HostAndPort) { - try { - assertEquals(proc.isAlive, true) - waitForNodeStartup(nodeAddr) - } catch (e: Throwable) { - println("Forcibly killing node process") - proc.destroyForcibly() - throw e + companion object { + val NODE_WAIT_RETRY_COUNT: Int = 50 + val NODE_WAIT_RETRY_DELAY_MS: Long = 200 + + fun ensureNodeStartsOrKill(proc: Process, nodeWebserverAddr: HostAndPort) { + try { + assertEquals(proc.isAlive, true) + waitForNodeStartup(nodeWebserverAddr) + } catch (e: Throwable) { + println("Forcibly killing node process") + proc.destroyForcibly() + throw e + } + } + + private fun waitForNodeStartup(nodeWebserverAddr: HostAndPort) { + val url = URL("http://${nodeWebserverAddr.toString()}/api/status") + var retries = 0 + var respCode: Int + do { + retries++ + val err = try { + val conn = url.openConnection() as HttpURLConnection + conn.requestMethod = "GET" + respCode = conn.responseCode + InputStreamReader(conn.inputStream).readLines().joinToString { it } + } catch(e: ConnectException) { + // This is to be expected while it loads up + respCode = 404 + "Node hasn't started" + } catch(e: SocketException) { + respCode = -1 + "Could not connect: ${e.toString()}" + } catch (e: IOException) { + respCode = -1 + "IOException: ${e.toString()}" + } + + if (retries > NODE_WAIT_RETRY_COUNT) { + throw NodeDidNotStartException("The node did not start: " + err) + } + + Thread.sleep(NODE_WAIT_RETRY_DELAY_MS) + } while (respCode != 200) + } } } - -private fun waitForNodeStartup(nodeAddr: HostAndPort) { - val url = URL("http://${nodeAddr.toString()}/api/status") - var retries = 0 - var respCode: Int - do { - retries++ - val err = try { - val conn = url.openConnection() as HttpURLConnection - conn.requestMethod = "GET" - respCode = conn.responseCode - InputStreamReader(conn.inputStream).readLines().joinToString { it } - } catch(e: ConnectException) { - // This is to be expected while it loads up - respCode = 404 - "Node hasn't started" - } catch(e: SocketException) { - respCode = -1 - "Could not connect: ${e.toString()}" - } catch (e: IOException) { - respCode = -1 - "IOException: ${e.toString()}" - } - - if (retries > 25) { - throw NodeDidNotStartException("The node did not start: " + err) - } - } while (respCode != 200) -} From 1e15e7a206ada50ad2b53fdad7d2b0011d765ee6 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Tue, 28 Jun 2016 13:56:55 +0100 Subject: [PATCH 45/48] trader-demo: Put demo directory under build/, add corresponding cli option --- src/main/kotlin/com/r3corda/demos/TraderDemo.kt | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt index 83ecc5c418..8178309c4b 100644 --- a/src/main/kotlin/com/r3corda/demos/TraderDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/TraderDemo.kt @@ -64,7 +64,7 @@ enum class Role { // And this is the directory under the current working directory where each node will create its own server directory, // which holds things like checkpoints, keys, databases, message logs etc. -val DIRNAME = "trader-demo" +val DEFAULT_BASE_DIRECTORY = "./build/trader-demo" fun main(args: Array) { exitProcess(runTraderDemo(args)) @@ -78,6 +78,7 @@ fun runTraderDemo(args: Array): Int { val myNetworkAddress = parser.accepts("network-address").withRequiredArg().defaultsTo("localhost") val theirNetworkAddress = parser.accepts("other-network-address").withRequiredArg().defaultsTo("localhost") val apiNetworkAddress = parser.accepts("api-address").withRequiredArg().defaultsTo("localhost") + val baseDirectoryArg = parser.accepts("base-directory").withRequiredArg().defaultsTo(DEFAULT_BASE_DIRECTORY) val options = try { parser.parse(*args) @@ -103,13 +104,16 @@ fun runTraderDemo(args: Array): Int { ) val apiNetAddr = HostAndPort.fromString(options.valueOf(apiNetworkAddress)).withDefaultPort(myNetAddr.port + 1) + val baseDirectory = options.valueOf(baseDirectoryArg)!! + // Suppress the Artemis MQ noise, and activate the demo logging. // // The first two strings correspond to the first argument to StateMachineManager.add() but the way we handle logging // for protocols will change in future. BriefLogFormatter.initVerbose("+demo.buyer", "+demo.seller", "-org.apache.activemq") - val directory = Paths.get(DIRNAME, role.name.toLowerCase()) + val directory = Paths.get(baseDirectory, role.name.toLowerCase()) + println("Using base demo directory $directory") // Override the default config file (which you can find in the file "reference.conf") to give each node a name. val config = run { @@ -136,7 +140,7 @@ fun runTraderDemo(args: Array): Int { // be a single shared map service (this is analagous to the DNS seeds in Bitcoin). // // TODO: AbstractNode should write out the full NodeInfo object and we should just load it here. - val path = Paths.get(DIRNAME, Role.BUYER.name.toLowerCase(), "identity-public") + val path = Paths.get(baseDirectory, Role.BUYER.name.toLowerCase(), "identity-public") val party = Files.readAllBytes(path).deserialize() advertisedServices = emptySet() cashIssuer = party From 54967afb788c88ea1b6562dc1f7f9ae5f88a68ac Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Tue, 28 Jun 2016 13:59:48 +0100 Subject: [PATCH 46/48] irs-demo: Rework CLI parsing, add api-address --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 491 ++++++++++--------- 1 file changed, 258 insertions(+), 233 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 48022c022a..8c3914be50 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -24,7 +24,6 @@ import com.r3corda.node.internal.testing.MockNetwork import com.r3corda.node.services.transactions.SimpleNotaryService import joptsimple.OptionParser import joptsimple.OptionSet -import joptsimple.OptionSpec import java.io.DataOutputStream import java.io.File import java.net.HttpURLConnection @@ -41,11 +40,14 @@ import java.net.SocketTimeoutException // IRS DEMO // // Please see docs/build/html/running-the-trading-demo.html -// -// TODO: TBD -// -// The different roles in the scenario this program can adopt are: +/** + * Roles. There are 4 modes this demo can be run: + * - SetupNodeA/SetupNodeB: Creates and sets up the necessary directories for nodes + * - NodeA/NodeB: Starts the nodes themselves + * - Trade: Uploads an example trade + * - DateChange: Changes the demo's date + */ enum class IRSDemoRole { SetupNodeA, SetupNodeB, @@ -55,28 +57,185 @@ enum class IRSDemoRole { Date } -private class NodeParams() { - var dir : Path = Paths.get("") - var address : HostAndPort = HostAndPort.fromString("localhost").withDefaultPort(Node.DEFAULT_PORT) - var mapAddress: String = "" - var apiAddress: HostAndPort = HostAndPort.fromString("localhost").withDefaultPort(Node.DEFAULT_PORT + 1) - var identityFile: Path = Paths.get("") - var tradeWithAddrs: List = listOf() - var tradeWithIdentities: List = listOf() - var uploadRates: Boolean = false - var defaultLegalName: String = "" +/** + * Parsed command line parameters. + */ +sealed class CliParams { + + /** + * Corresponds to roles 'SetupNodeA' and 'SetupNodeB' + */ + class SetupNode( + val node: IRSDemoNode, + val dir: Path, + val defaultLegalName: String + ) : CliParams() + + /** + * Corresponds to roles 'NodeA' and 'NodeB' + */ + class RunNode( + val node: IRSDemoNode, + val dir: Path, + val networkAddress : HostAndPort, + val apiAddress: HostAndPort, + val mapAddress: String, + val identityFile: Path, + val tradeWithAddrs: List, + val tradeWithIdentities: List, + val uploadRates: Boolean, + val defaultLegalName: String, + val autoSetup: Boolean // Run Setup for both nodes automatically with default arguments + ) : CliParams() + + /** + * Corresponds to role 'Trade' + */ + class Trade( + val apiAddress: HostAndPort, + val tradeId: String + ) : CliParams() + + /** + * Corresponds to role 'Date' + */ + class DateChange( + val apiAddress: HostAndPort, + val dateString: String + ) : CliParams() + + companion object { + + val defaultBaseDirectory = "./build/irs-demo" + + fun legalName(node: IRSDemoNode) = + when (node) { + IRSDemoNode.NodeA -> "Bank A" + IRSDemoNode.NodeB -> "Bank B" + } + + private fun nodeDirectory(options: OptionSet, node: IRSDemoNode) = + Paths.get(options.valueOf(CliParamsSpec.baseDirectoryArg), node.name.decapitalize()) + + private fun parseSetupNode(options: OptionSet, node: IRSDemoNode): SetupNode { + return SetupNode( + node = node, + dir = nodeDirectory(options, node), + defaultLegalName = legalName(node) + ) + } + + private fun defaultNetworkPort(node: IRSDemoNode) = + when (node) { + IRSDemoNode.NodeA -> Node.DEFAULT_PORT + IRSDemoNode.NodeB -> Node.DEFAULT_PORT + 2 + } + + private fun defaultApiPort(node: IRSDemoNode) = + when (node) { + IRSDemoNode.NodeA -> Node.DEFAULT_PORT + 1 + IRSDemoNode.NodeB -> Node.DEFAULT_PORT + 3 + } + + private fun parseRunNode(options: OptionSet, node: IRSDemoNode): RunNode { + val dir = nodeDirectory(options, node) + + return RunNode( + node = node, + dir = dir, + networkAddress = HostAndPort.fromString(options.valueOf( + CliParamsSpec.networkAddressArg.defaultsTo("localhost:${defaultNetworkPort(node)}") + )), + apiAddress = HostAndPort.fromString(options.valueOf( + CliParamsSpec.apiAddressArg.defaultsTo("localhost:${defaultApiPort(node)}") + )), + mapAddress = options.valueOf(CliParamsSpec.networkMapNetAddr), + identityFile = if (options.has(CliParamsSpec.networkMapIdentityFile)) { + Paths.get(options.valueOf(CliParamsSpec.networkMapIdentityFile)) + } else { + dir.resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME) + }, + tradeWithAddrs = if (options.has(CliParamsSpec.fakeTradeWithAddr)) { + options.valuesOf(CliParamsSpec.fakeTradeWithAddr) + } else { + listOf("localhost:${defaultNetworkPort(node.other)}") + }, + tradeWithIdentities = if (options.has(CliParamsSpec.fakeTradeWithIdentityFile)) { + options.valuesOf(CliParamsSpec.fakeTradeWithIdentityFile).map { Paths.get(it) } + } else { + listOf(nodeDirectory(options, node.other).resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME)) + }, + uploadRates = node == IRSDemoNode.NodeB, + defaultLegalName = legalName(node), + autoSetup = !options.has(CliParamsSpec.baseDirectoryArg) && !options.has(CliParamsSpec.fakeTradeWithIdentityFile) + ) + } + + private fun parseTrade(options: OptionSet): Trade { + return Trade( + apiAddress = HostAndPort.fromString(options.valueOf( + CliParamsSpec.apiAddressArg.defaultsTo("localhost:${defaultApiPort(IRSDemoNode.NodeA)}") + )), + tradeId = options.valuesOf(CliParamsSpec.nonOptions).let { + if (it.size > 0) { + it[0] + } else { + throw IllegalArgumentException("Please provide a trade ID") + } + } + ) + } + + private fun parseDateChange(options: OptionSet): DateChange { + return DateChange( + apiAddress = HostAndPort.fromString(options.valueOf(CliParamsSpec.apiAddressArg)), + dateString = options.valuesOf(CliParamsSpec.nonOptions).let { + if (it.size > 0) { + it[0] + } else { + throw IllegalArgumentException("Please provide a date string") + } + } + ) + } + + fun parse(options: OptionSet): CliParams { + val role = options.valueOf(CliParamsSpec.roleArg)!! + return when (role) { + IRSDemoRole.SetupNodeA -> parseSetupNode(options, IRSDemoNode.NodeA) + IRSDemoRole.SetupNodeB -> parseSetupNode(options, IRSDemoNode.NodeB) + IRSDemoRole.NodeA -> parseRunNode(options, IRSDemoNode.NodeA) + IRSDemoRole.NodeB -> parseRunNode(options, IRSDemoNode.NodeB) + IRSDemoRole.Trade -> parseTrade(options) + IRSDemoRole.Date -> parseDateChange(options) + } + } + } } -private class DemoArgs() { - lateinit var roleArg: OptionSpec - lateinit var networkAddressArg: OptionSpec - lateinit var apiAddressArg: OptionSpec - lateinit var dirArg: OptionSpec - lateinit var networkMapIdentityFile: OptionSpec - lateinit var networkMapNetAddr: OptionSpec - lateinit var fakeTradeWithAddr: OptionSpec - lateinit var fakeTradeWithIdentityFile: OptionSpec - lateinit var nonOptions: OptionSpec +enum class IRSDemoNode { + NodeA, + NodeB; + + val other: IRSDemoNode get() { + return when (this) { + NodeA -> NodeB + NodeB -> NodeA + } + } +} + +object CliParamsSpec { + val parser = OptionParser() + val roleArg = parser.accepts("role").withRequiredArg().ofType(IRSDemoRole::class.java) + val networkAddressArg = parser.accepts("network-address").withOptionalArg().ofType(String::class.java) + val apiAddressArg = parser.accepts("api-address").withOptionalArg().ofType(String::class.java) + val baseDirectoryArg = parser.accepts("base-directory").withOptionalArg().defaultsTo(CliParams.defaultBaseDirectory) + val networkMapIdentityFile = parser.accepts("network-map-identity-file").withOptionalArg() + val networkMapNetAddr = parser.accepts("network-map-address").withRequiredArg().defaultsTo("localhost") + val fakeTradeWithAddr = parser.accepts("fake-trade-with-address").withOptionalArg() + val fakeTradeWithIdentityFile = parser.accepts("fake-trade-with-identity-file").withOptionalArg() + val nonOptions = parser.nonOptions() } private class NotSetupException: Throwable { @@ -88,12 +247,10 @@ fun main(args: Array) { } fun runIRSDemo(args: Array): Int { - val parser = OptionParser() - val demoArgs = setupArgs(parser) - val options = try { - parser.parse(*args) + val cliParams = try { + CliParams.parse(CliParamsSpec.parser.parse(*args)) } catch (e: Exception) { - println(e.message) + println(e) printHelp() return 1 } @@ -101,69 +258,62 @@ fun runIRSDemo(args: Array): Int { // Suppress the Artemis MQ noise, and activate the demo logging. BriefLogFormatter.initVerbose("+demo.irsdemo", "+api-call", "+platform.deal", "-org.apache.activemq") - val role = options.valueOf(demoArgs.roleArg)!! - return when (role) { - IRSDemoRole.SetupNodeA -> setup(configureNodeParams(IRSDemoRole.NodeA, demoArgs, options)) - IRSDemoRole.SetupNodeB -> setup(configureNodeParams(IRSDemoRole.NodeB, demoArgs, options)) - IRSDemoRole.NodeA -> runNode(role, demoArgs, options) - IRSDemoRole.NodeB -> runNode(role, demoArgs, options) - IRSDemoRole.Trade -> runTrade(demoArgs, options) - IRSDemoRole.Date -> runDateChange(demoArgs, options) + return when (cliParams) { + is CliParams.SetupNode -> setup(cliParams) + is CliParams.RunNode -> runNode(cliParams) + is CliParams.Trade -> runTrade(cliParams) + is CliParams.DateChange -> runDateChange(cliParams) } } -private fun runTrade(demoArgs: DemoArgs, options: OptionSet): Int { - val tradeIdArgs = options.valuesOf(demoArgs.nonOptions) - if (tradeIdArgs.size > 0) { - val tradeId = tradeIdArgs[0] - val host = if (options.has(demoArgs.networkAddressArg)) { - options.valueOf(demoArgs.networkAddressArg) - } else { - "http://localhost:" + (Node.DEFAULT_PORT + 1) - } - - if (!uploadTrade(tradeId, host)) { - return 1 - } - } else { - println("Please provide a trade ID") - return 1 +private fun setup(params: CliParams.SetupNode): Int { + val dirFile = params.dir.toFile() + if (!dirFile.exists()) { + dirFile.mkdirs() } + val configFile = params.dir.resolve("config").toFile() + val config = loadConfigFile(configFile, params.defaultLegalName) + if (!Files.exists(params.dir.resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME))) { + createIdentities(params, config) + } return 0 } -private fun runDateChange(demoArgs: DemoArgs, options: OptionSet): Int { - val dateStrArgs = options.valuesOf(demoArgs.nonOptions) - if (dateStrArgs.size > 0) { - val dateStr = dateStrArgs[0] - val host = if (options.has(demoArgs.networkAddressArg)) { - options.valueOf(demoArgs.networkAddressArg) - } else { - "http://localhost:" + (Node.DEFAULT_PORT + 1) - } +private fun defaultNodeSetupParams(node: IRSDemoNode): CliParams.SetupNode = + CliParams.SetupNode( + node = node, + dir = Paths.get(CliParams.defaultBaseDirectory, node.name.decapitalize()), + defaultLegalName = CliParams.legalName(node) + ) - if (!changeDate(dateStr, host)) { - return 1 - } - } else { - println("Please provide a date") - return 1 - } - - return 0 -} - -private fun runNode(role: IRSDemoRole, demoArgs: DemoArgs, options: OptionSet): Int { - // If these directory and identity file arguments aren't specified then we can assume a default setup and - // create everything that is needed without needing to run setup. - if (!options.has(demoArgs.dirArg) && !options.has(demoArgs.fakeTradeWithIdentityFile)) { - createNodeConfig(createNodeAParams()); - createNodeConfig(createNodeBParams()); +private fun runNode(cliParams: CliParams.RunNode): Int { + if (cliParams.autoSetup) { + setup(defaultNodeSetupParams(IRSDemoNode.NodeA)) + setup(defaultNodeSetupParams(IRSDemoNode.NodeB)) } try { - runNode(configureNodeParams(role, demoArgs, options)) + val networkMap = createRecipient(cliParams.mapAddress) + val destinations = cliParams.tradeWithAddrs.map({ + createRecipient(it) + }) + + val node = startNode(cliParams, networkMap, destinations) + // Register handlers for the demo + AutoOfferProtocol.Handler.register(node) + UpdateBusinessDayProtocol.Handler.register(node) + ExitServerProtocol.Handler.register(node) + + if (cliParams.uploadRates) { + runUploadRates(cliParams.apiAddress) + } + + try { + while (true) Thread.sleep(Long.MAX_VALUE) + } catch(e: InterruptedException) { + node.stop() + } } catch (e: NotSetupException) { println(e.message) return 1 @@ -172,109 +322,30 @@ private fun runNode(role: IRSDemoRole, demoArgs: DemoArgs, options: OptionSet): return 0 } -private fun setupArgs(parser: OptionParser): DemoArgs { - val args = DemoArgs() - - args.roleArg = parser.accepts("role").withRequiredArg().ofType(IRSDemoRole::class.java).required() - args.networkAddressArg = parser.accepts("network-address").withOptionalArg() - args.apiAddressArg = parser.accepts("api-address").withOptionalArg() - args.dirArg = parser.accepts("directory").withOptionalArg() - args.networkMapIdentityFile = parser.accepts("network-map-identity-file").withOptionalArg() - args.networkMapNetAddr = parser.accepts("network-map-address").withRequiredArg().defaultsTo("localhost") - // Use these to list one or more peers (again, will be superseded by discovery implementation) - args.fakeTradeWithAddr = parser.accepts("fake-trade-with-address").withOptionalArg() - args.fakeTradeWithIdentityFile = parser.accepts("fake-trade-with-identity-file").withOptionalArg() - args.nonOptions = parser.nonOptions().ofType(String::class.java) - - return args -} - -private fun setup(params: NodeParams): Int { - createNodeConfig(params) - return 0 -} - -private fun changeDate(date: String, host: String) : Boolean { - println("Changing date to " + date) - val url = URL(host + "/api/irs/demodate") - if (putJson(url, "\"" + date + "\"")) { +private fun runDateChange(cliParams: CliParams.DateChange): Int { + println("Changing date to " + cliParams.dateString) + val url = URL("http://${cliParams.apiAddress}/api/irs/demodate") + if (putJson(url, "\"" + cliParams.dateString + "\"")) { println("Date changed") - return true + return 0 } else { println("Date failed to change") - return false + return 1 } } -private fun uploadTrade(tradeId: String, host: String) : Boolean { - println("Uploading tradeID " + tradeId) - val fileContents = IOUtils.toString(NodeParams::class.java.getResourceAsStream("example-irs-trade.json")) - val tradeFile = fileContents.replace("tradeXXX", tradeId) - val url = URL(host + "/api/irs/deals") +private fun runTrade(cliParams: CliParams.Trade): Int { + println("Uploading tradeID " + cliParams.tradeId) + // Note: the getResourceAsStream is an ugly hack to get the jvm to search in the right location + val fileContents = IOUtils.toString(CliParams::class.java.getResourceAsStream("example-irs-trade.json")) + val tradeFile = fileContents.replace("tradeXXX", cliParams.tradeId) + val url = URL("http://${cliParams.apiAddress}/api/irs/deals") if (postJson(url, tradeFile)) { println("Trade sent") - return true + return 0 } else { println("Trade failed to send") - return false - } -} - -private fun configureNodeParams(role: IRSDemoRole, args: DemoArgs, options: OptionSet): NodeParams { - val nodeParams = when (role) { - IRSDemoRole.NodeA -> createNodeAParams() - IRSDemoRole.NodeB -> createNodeBParams() - else -> { - throw IllegalArgumentException() - } - } - - nodeParams.mapAddress = options.valueOf(args.networkMapNetAddr) - if (options.has(args.dirArg)) { - nodeParams.dir = Paths.get(options.valueOf(args.dirArg)) - } - if (options.has(args.networkAddressArg)) { - nodeParams.address = HostAndPort.fromString(options.valueOf(args.networkAddressArg)).withDefaultPort(Node.DEFAULT_PORT) - } - if (options.has(args.apiAddressArg)) { - nodeParams.apiAddress = HostAndPort.fromString(options.valueOf(args.apiAddressArg)).withDefaultPort(Node.DEFAULT_PORT + 1) - } - - nodeParams.identityFile = if (options.has(args.networkMapIdentityFile)) { - Paths.get(options.valueOf(args.networkMapIdentityFile)) - } else { - nodeParams.dir.resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME) - } - if (options.has(args.fakeTradeWithIdentityFile)) { - nodeParams.tradeWithIdentities = options.valuesOf(args.fakeTradeWithIdentityFile).map { Paths.get(it) } - } - if (options.has(args.fakeTradeWithAddr)) { - nodeParams.tradeWithAddrs = options.valuesOf(args.fakeTradeWithAddr) - } - - return nodeParams -} - -private fun runNode(nodeParams: NodeParams): Unit { - val networkMap = createRecipient(nodeParams.mapAddress) - val destinations = nodeParams.tradeWithAddrs.map({ - createRecipient(it) - }) - - val node = startNode(nodeParams, networkMap, destinations) - // Register handlers for the demo - AutoOfferProtocol.Handler.register(node) - UpdateBusinessDayProtocol.Handler.register(node) - ExitServerProtocol.Handler.register(node) - - if (nodeParams.uploadRates) { - runUploadRates(nodeParams.apiAddress) - } - - try { - while (true) Thread.sleep(Long.MAX_VALUE) - } catch(e: InterruptedException) { - node.stop() + return 1 } } @@ -283,7 +354,7 @@ private fun createRecipient(addr: String) : SingleMessageRecipient { return ArtemisMessagingService.makeRecipient(hostAndPort) } -private fun startNode(params : NodeParams, networkMap: SingleMessageRecipient, recipients: List) : Node { +private fun startNode(params: CliParams.RunNode, networkMap: SingleMessageRecipient, recipients: List) : Node { val config = getNodeConfig(params) val advertisedServices: Set val networkMapId = if (params.mapAddress.equals(params.address.toString())) { @@ -296,7 +367,7 @@ private fun startNode(params : NodeParams, networkMap: SingleMessageRecipient, r } val node = logElapsedTime("Node startup") { - Node(params.dir, params.address, params.apiAddress, config, networkMapId, advertisedServices, DemoClock(), + Node(params.dir, params.networkAddress, params.apiAddress, config, networkMapId, advertisedServices, DemoClock(), listOf(InterestRateSwapAPI::class.java)).start() } @@ -325,7 +396,8 @@ private fun nodeInfo(recipient: SingleMessageRecipient, identityFile: Path, adve } private fun runUploadRates(host: HostAndPort) { - val fileContents = IOUtils.toString(NodeParams::class.java.getResource("example.rates.txt")) + // Note: the getResourceAsStream is an ugly hack to get the jvm to search in the right location + val fileContents = IOUtils.toString(CliParams::class.java.getResourceAsStream("example.rates.txt")) var timer : Timer? = null timer = fixedRateTimer("upload-rates", false, 0, 5000, { try { @@ -364,7 +436,7 @@ private fun sendJson(url: URL, data: String, method: String) : Boolean { 200 -> true 201 -> true else -> { - println("Failed to " + method + " data. Status Code: " + connection.responseCode + ". Mesage: " + connection.responseMessage) + println("Failed to " + method + " data. Status Code: " + connection.responseCode + ". Message: " + connection.responseMessage) false } } @@ -401,7 +473,7 @@ private fun uploadFile(url: URL, file: String) : Boolean { val request = DataOutputStream(connection.outputStream) request.writeBytes(hyphens + boundary + clrf) - request.writeBytes("Content-Disposition: form-data; name=\"rates\" filename=\"example.rates.txt\"" + clrf) + request.writeBytes("Content-Disposition: form-data; name=\"rates\" filename=\"example.rates.txt\"$clrf") request.writeBytes(clrf) request.writeBytes(file) request.writeBytes(clrf) @@ -410,69 +482,22 @@ private fun uploadFile(url: URL, file: String) : Boolean { if (connection.responseCode == 200) { return true } else { - println("Could not upload file. Status Code: " + connection + ". Mesage: " + connection.responseMessage) + println("Could not upload file. Status Code: " + connection + ". Message: " + connection.responseMessage) return false } } -private fun createNodeAParams() : NodeParams { - val params = NodeParams() - params.dir = Paths.get("nodeA") - params.address = HostAndPort.fromString("localhost").withDefaultPort(Node.DEFAULT_PORT) - params.apiAddress = HostAndPort.fromString("localhost").withDefaultPort(Node.DEFAULT_PORT + 1) - params.tradeWithAddrs = listOf("localhost:31340") - params.tradeWithIdentities = listOf(getRoleDir(IRSDemoRole.NodeB).resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME)) - params.defaultLegalName = "Bank A" - return params -} - -private fun createNodeBParams() : NodeParams { - val params = NodeParams() - params.dir = Paths.get("nodeB") - params.address = HostAndPort.fromString("localhost:31340") - params.apiAddress = HostAndPort.fromString("localhost:31341") - params.tradeWithAddrs = listOf("localhost") - params.tradeWithIdentities = listOf(getRoleDir(IRSDemoRole.NodeA).resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME)) - params.defaultLegalName = "Bank B" - params.uploadRates = true - return params -} - -private fun createNodeConfig(params: NodeParams) : NodeConfiguration { - if (!Files.exists(params.dir)) { - Files.createDirectory(params.dir) - } - - val configFile = params.dir.resolve("config").toFile() - val config = loadConfigFile(configFile, params.defaultLegalName) - if (!Files.exists(params.dir.resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME))) { - createIdentities(params, config) - } - - return config -} - -private fun getNodeConfig(params: NodeParams): NodeConfiguration { - if (!Files.exists(params.dir)) { +private fun getNodeConfig(cliParams: CliParams.RunNode): NodeConfiguration { + if (!Files.exists(cliParams.dir)) { throw NotSetupException("Missing config directory. Please run node setup before running the node") } - if (!Files.exists(params.dir.resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME))) { + if (!Files.exists(cliParams.dir.resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME))) { throw NotSetupException("Missing identity file. Please run node setup before running the node") } - val configFile = params.dir.resolve("config").toFile() - return loadConfigFile(configFile, params.defaultLegalName) -} - -private fun getRoleDir(role: IRSDemoRole) : Path { - when (role) { - IRSDemoRole.NodeA -> return Paths.get("nodeA") - IRSDemoRole.NodeB -> return Paths.get("nodeB") - else -> { - throw IllegalArgumentException() - } - } + val configFile = cliParams.dir.resolve("config").toFile() + return loadConfigFile(configFile, cliParams.defaultLegalName) } private fun loadConfigFile(configFile: File, defaultLegalName: String): NodeConfiguration { @@ -485,7 +510,7 @@ private fun loadConfigFile(configFile: File, defaultLegalName: String): NodeConf return NodeConfigurationFromConfig(config) } -private fun createIdentities(params: NodeParams, nodeConf: NodeConfiguration) { +private fun createIdentities(params: CliParams.SetupNode, nodeConf: NodeConfiguration) { val mockNetwork = MockNetwork(false) val node = MockNetwork.MockNode(params.dir, nodeConf, mockNetwork, null, setOf(NetworkMapService.Type, SimpleNotaryService.Type), 0, null) node.start() From aa82d4441e378566a50ced7c15d4f3be59aefc17 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Tue, 28 Jun 2016 14:00:49 +0100 Subject: [PATCH 47/48] irs-demo: Fix advertised services --- src/main/kotlin/com/r3corda/demos/IRSDemo.kt | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt index 8c3914be50..e1d18d390a 100644 --- a/src/main/kotlin/com/r3corda/demos/IRSDemo.kt +++ b/src/main/kotlin/com/r3corda/demos/IRSDemo.kt @@ -357,14 +357,17 @@ private fun createRecipient(addr: String) : SingleMessageRecipient { private fun startNode(params: CliParams.RunNode, networkMap: SingleMessageRecipient, recipients: List) : Node { val config = getNodeConfig(params) val advertisedServices: Set - val networkMapId = if (params.mapAddress.equals(params.address.toString())) { - // This node provides network map and notary services - advertisedServices = setOf(NetworkMapService.Type, SimpleNotaryService.Type) - null - } else { - advertisedServices = setOf(NodeInterestRates.Type) - nodeInfo(networkMap, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type)) - } + val networkMapId = + when (params.node) { + IRSDemoNode.NodeA -> { + advertisedServices = setOf(NetworkMapService.Type, SimpleNotaryService.Type) + null + } + IRSDemoNode.NodeB -> { + advertisedServices = setOf(NodeInterestRates.Type) + nodeInfo(networkMap, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type)) + } + } val node = logElapsedTime("Node startup") { Node(params.dir, params.networkAddress, params.apiAddress, config, networkMapId, advertisedServices, DemoClock(), From 9e5bf7c32c2e9127238409256ef507569a09bb19 Mon Sep 17 00:00:00 2001 From: Andras Slemmer Date: Tue, 28 Jun 2016 14:03:10 +0100 Subject: [PATCH 48/48] integtest: Use separate folder for each integtest run, provide api address --- .../com/r3corda/core/testing/IRSDemoTest.kt | 47 +++++++------ .../r3corda/core/testing/TraderDemoTest.kt | 70 +++++++++---------- .../core/testing/utilities/TestTimestamp.kt | 19 +++++ 3 files changed, 81 insertions(+), 55 deletions(-) create mode 100644 src/integration-test/kotlin/com/r3corda/core/testing/utilities/TestTimestamp.kt diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt index a0e653525d..31cc7f9f22 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/IRSDemoTest.kt @@ -12,44 +12,56 @@ class IRSDemoTest { val nodeAddrA = freeLocalHostAndPort() val apiAddrA = freeLocalHostAndPort() val apiAddrB = freeLocalHostAndPort() - val dirA = Paths.get("./nodeA") - val dirB = Paths.get("./nodeB") + + val baseDirectory = Paths.get("./build/integration-test/${TestTimestamp.timestamp}/irs-demo") var procA: Process? = null var procB: Process? = null try { - setupNode(dirA, "NodeA") - setupNode(dirB, "NodeB") - procA = startNode(dirA, "NodeA", nodeAddrA, nodeAddrA, apiAddrA) - procB = startNode(dirB, "NodeB", freeLocalHostAndPort(), nodeAddrA, apiAddrB) + setupNode(baseDirectory, "NodeA") + setupNode(baseDirectory, "NodeB") + procA = startNode( + baseDirectory = baseDirectory, + nodeType = "NodeA", + nodeAddr = nodeAddrA, + networkMapAddr = apiAddrA, + apiAddr = apiAddrA + ) + procB = startNode( + baseDirectory = baseDirectory, + nodeType = "NodeB", + nodeAddr = freeLocalHostAndPort(), + networkMapAddr = nodeAddrA, + apiAddr = apiAddrB + ) runTrade(apiAddrA) runDateChange(apiAddrA) } finally { stopNode(procA) stopNode(procB) - cleanup(dirA) - cleanup(dirB) } } } -private fun setupNode(dir: Path, nodeType: String) { +private fun setupNode(baseDirectory: Path, nodeType: String) { println("Running setup for $nodeType") - val args = listOf("--role", "Setup" + nodeType, "--dir", dir.toString()) + val args = listOf("--role", "Setup" + nodeType, "--base-directory", baseDirectory.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args, "IRSDemoSetup$nodeType") assertExitOrKill(proc) assertEquals(proc.exitValue(), 0) } -private fun startNode(dir: Path, +private fun startNode(baseDirectory: Path, nodeType: String, nodeAddr: HostAndPort, networkMapAddr: HostAndPort, apiAddr: HostAndPort): Process { println("Running node $nodeType") - println("Node addr: ${nodeAddr.toString()}") + println("Node addr: $nodeAddr") + println("Network map addr: $networkMapAddr") + println("API addr: $apiAddr") val args = listOf( "--role", nodeType, - "--dir", dir.toString(), + "--base-directory", baseDirectory.toString(), "--network-address", nodeAddr.toString(), "--network-map-address", networkMapAddr.toString(), "--api-address", apiAddr.toString()) @@ -60,7 +72,7 @@ private fun startNode(dir: Path, private fun runTrade(nodeAddr: HostAndPort) { println("Running trade") - val args = listOf("--role", "Trade", "trade1", "--network-address", "http://" + nodeAddr.toString()) + val args = listOf("--role", "Trade", "trade1", "--api-address", nodeAddr.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args, "IRSDemoTrade") assertExitOrKill(proc) assertEquals(proc.exitValue(), 0) @@ -68,7 +80,7 @@ private fun runTrade(nodeAddr: HostAndPort) { private fun runDateChange(nodeAddr: HostAndPort) { println("Running date change") - val args = listOf("--role", "Date", "2017-01-02", "--network-address", "http://" + nodeAddr.toString()) + val args = listOf("--role", "Date", "2017-01-02", "--api-address", nodeAddr.toString()) val proc = spawn("com.r3corda.demos.IRSDemoKt", args, "IRSDemoDate") assertExitOrKill(proc) assertEquals(proc.exitValue(), 0) @@ -80,8 +92,3 @@ private fun stopNode(nodeProc: Process?) { assertAliveAndKill(nodeProc) } } - -private fun cleanup(dir: Path) { - println("Erasing: " + dir.toString()) - dir.toFile().deleteRecursively() -} diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt index 5a48ff5a28..57d8d49ad2 100644 --- a/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt +++ b/src/integration-test/kotlin/com/r3corda/core/testing/TraderDemoTest.kt @@ -2,58 +2,58 @@ package com.r3corda.core.testing import com.google.common.net.HostAndPort import com.r3corda.core.testing.utilities.NodeApi +import com.r3corda.core.testing.utilities.TestTimestamp import com.r3corda.core.testing.utilities.assertExitOrKill import com.r3corda.core.testing.utilities.spawn import org.junit.Test import java.nio.file.Paths +import java.text.SimpleDateFormat +import java.util.* import kotlin.test.assertEquals class TraderDemoTest { @Test fun `runs trader demo`() { val buyerAddr = freeLocalHostAndPort() val buyerApiAddr = freeLocalHostAndPort() + val directory = "./build/integration-test/${TestTimestamp.timestamp}/trader-demo" var nodeProc: Process? = null try { - cleanupFiles() - nodeProc = runBuyer(buyerAddr, buyerApiAddr) - runSeller(buyerAddr) + nodeProc = runBuyer(directory, buyerAddr, buyerApiAddr) + runSeller(directory, buyerAddr) } finally { nodeProc?.destroy() - cleanupFiles() } } -} -private fun runBuyer(buyerAddr: HostAndPort, buyerApiAddr: HostAndPort): Process { - println("Running Buyer") - val args = listOf( - "--role", "BUYER", - "--network-address", buyerAddr.toString(), - "--api-address", buyerApiAddr.toString() - ) - val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoBuyer") - NodeApi.ensureNodeStartsOrKill(proc, buyerApiAddr) - return proc -} + companion object { + private fun runBuyer(baseDirectory: String, buyerAddr: HostAndPort, buyerApiAddr: HostAndPort): Process { + println("Running Buyer") + val args = listOf( + "--role", "BUYER", + "--network-address", buyerAddr.toString(), + "--api-address", buyerApiAddr.toString(), + "--base-directory", baseDirectory + ) + val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoBuyer") + NodeApi.ensureNodeStartsOrKill(proc, buyerApiAddr) + return proc + } -private fun runSeller(buyerAddr: HostAndPort) { - println("Running Seller") - val sellerAddr = freeLocalHostAndPort() - val sellerApiAddr = freeLocalHostAndPort() - val args = listOf( - "--role", "SELLER", - "--network-address", sellerAddr.toString(), - "--api-address", sellerApiAddr.toString(), - "--other-network-address", buyerAddr.toString() - ) - val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoSeller") - assertExitOrKill(proc); - assertEquals(proc.exitValue(), 0) -} + private fun runSeller(baseDirectory: String, buyerAddr: HostAndPort) { + println("Running Seller") + val sellerAddr = freeLocalHostAndPort() + val sellerApiAddr = freeLocalHostAndPort() + val args = listOf( + "--role", "SELLER", + "--network-address", sellerAddr.toString(), + "--api-address", sellerApiAddr.toString(), + "--other-network-address", buyerAddr.toString(), + "--base-directory", baseDirectory + ) + val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoSeller") + assertExitOrKill(proc); + assertEquals(proc.exitValue(), 0) + } -private fun cleanupFiles() { - println("Cleaning up TraderDemoTest files") - val dir = Paths.get("trader-demo") - println("Erasing " + dir) - dir.toFile().deleteRecursively() + } } diff --git a/src/integration-test/kotlin/com/r3corda/core/testing/utilities/TestTimestamp.kt b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/TestTimestamp.kt new file mode 100644 index 0000000000..43bde8be70 --- /dev/null +++ b/src/integration-test/kotlin/com/r3corda/core/testing/utilities/TestTimestamp.kt @@ -0,0 +1,19 @@ +package com.r3corda.core.testing.utilities + +import java.text.SimpleDateFormat +import java.util.* + +/** + * [timestamp] holds a formatted (UTC) timestamp that's set the first time it is queried. This is used to + * provide a uniform timestamp for tests + */ +class TestTimestamp { + companion object { + val timestamp: String = { + val tz = TimeZone.getTimeZone("UTC") + val df = SimpleDateFormat("yyyy-MM-dd-HH:mm:ss") + df.timeZone = tz + df.format(Date()) + }() + } +}