Added setup steps to the IRSDemo to make steps more explicit and tidied up the handling of args.

This commit is contained in:
Clinton Alexander 2016-06-06 14:40:29 +01:00
parent 782d50958d
commit 8010836f14

View File

@ -20,8 +20,13 @@ import com.r3corda.demos.protocols.AutoOfferProtocol
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 joptsimple.OptionParser
import joptsimple.OptionSet
import joptsimple.OptionSpec
import java.io.DataOutputStream
import java.io.File
import java.net.HttpURLConnection
import java.net.URL
import java.nio.file.Files
@ -41,6 +46,8 @@ import org.apache.commons.io.IOUtils
// The different roles in the scenario this program can adopt are:
enum class IRSDemoRole {
SetupNodeA,
SetupNodeB,
NodeA,
NodeB,
Trade,
@ -48,6 +55,7 @@ enum class IRSDemoRole {
}
private class NodeParams() {
var id: Int = -1
var dir : Path = Paths.get("")
var address : String = ""
var mapAddress: String = ""
@ -58,23 +66,20 @@ private class NodeParams() {
var defaultLegalName: String = ""
}
private class DemoArgs() {
lateinit var roleArg: OptionSpec<IRSDemoRole>
lateinit var networkAddressArg: OptionSpec<String>
lateinit var dirArg: OptionSpec<String>
lateinit var networkMapIdentityFile: OptionSpec<String>
lateinit var networkMapNetAddr: OptionSpec<String>
lateinit var fakeTradeWithAddr: OptionSpec<String>
lateinit var fakeTradeWithIdentityFile: OptionSpec<String>
lateinit var nonOptions: OptionSpec<String>
}
fun main(args: Array<String>) {
val parser = OptionParser()
val roleArg = parser.accepts("role").withRequiredArg().ofType(IRSDemoRole::class.java).required()
val networkAddressArg = parser.accepts("network-address").withOptionalArg()
val dirArg = parser.accepts("directory").withOptionalArg()
val networkMapIdentityFile = parser.accepts("network-map-identity-file").withOptionalArg()
val networkMapNetAddr = parser.accepts("network-map-address").withRequiredArg().defaultsTo("localhost")
// Use these to list one or more peers (again, will be superseded by discovery implementation)
val fakeTradeWithAddr = parser.accepts("fake-trade-with-address").withOptionalArg()
val fakeTradeWithIdentityFile = parser.accepts("fake-trade-with-identity-file").withOptionalArg()
val nonOptions = parser.nonOptions().ofType(String::class.java)
val demoArgs = setupArgs(parser)
val options = try {
parser.parse(*args)
} catch (e: Exception) {
@ -86,15 +91,21 @@ fun main(args: Array<String>) {
// 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(roleArg)!!
if(role == IRSDemoRole.Trade) {
val tradeIdArgs = options.valuesOf(nonOptions)
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(networkAddressArg)) {
options.valueOf(networkAddressArg)
val host = if (options.has(demoArgs.networkAddressArg)) {
options.valueOf(demoArgs.networkAddressArg)
} else {
"http://localhost:" + Node.DEFAULT_PORT + 1
"http://localhost:" + (Node.DEFAULT_PORT + 1)
}
if (runTrade(tradeId, host)) {
@ -107,13 +118,13 @@ fun main(args: Array<String>) {
exitProcess(1)
}
} else if(role == IRSDemoRole.Date) {
val dateStrArgs = options.valuesOf(nonOptions)
val dateStrArgs = options.valuesOf(demoArgs.nonOptions)
if (dateStrArgs.size > 0) {
val dateStr = dateStrArgs[0]
val host = if (options.has(networkAddressArg)) {
options.valueOf(networkAddressArg)
val host = if (options.has(demoArgs.networkAddressArg)) {
options.valueOf(demoArgs.networkAddressArg)
} else {
"http://localhost:" + Node.DEFAULT_PORT + 1
"http://localhost:" + (Node.DEFAULT_PORT + 1)
}
runDateChange(dateStr, host)
@ -122,38 +133,32 @@ fun main(args: Array<String>) {
exitProcess(1)
}
} else {
val nodeParams = when (role) {
IRSDemoRole.NodeA -> createNodeAParams()
IRSDemoRole.NodeB -> createNodeBParams()
else -> {
throw IllegalArgumentException()
}
}
nodeParams.mapAddress = options.valueOf(networkMapNetAddr)
if (options.has(dirArg)) {
nodeParams.dir = Paths.get(options.valueOf(dirArg))
}
if (options.has(networkAddressArg)) {
nodeParams.address = options.valueOf(networkAddressArg)
}
nodeParams.identityFile = if (options.has(networkMapIdentityFile)) {
Paths.get(options.valueOf(networkMapIdentityFile))
} else {
nodeParams.dir.resolve("identity-public")
}
if (options.has(fakeTradeWithIdentityFile)) {
nodeParams.tradeWithIdentities = options.valuesOf(fakeTradeWithIdentityFile).map { Paths.get(it) }
}
if (options.has(fakeTradeWithAddr)) {
nodeParams.tradeWithAddrs = options.valuesOf(fakeTradeWithAddr)
}
val nodeParams = configureNodeParams(role, demoArgs, options)
runNode(nodeParams)
exitProcess(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.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) {
createNodeConfig(params)
}
private fun runDateChange(date: String, host: String) : Boolean {
val url = URL(host + "/api/irs/demodate")
if(putJson(url, "\"" + date + "\"")) {
@ -179,6 +184,37 @@ private fun runTrade(tradeId: String, host: String) : Boolean {
}
}
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 = options.valueOf(args.networkAddressArg)
}
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 node = startNode(nodeParams)
// Register handlers for the demo
@ -215,11 +251,14 @@ private fun runUploadRates() {
})
}
// Todo: Use a simpler library function for this and handle timeout exceptions
private fun sendJson(url: URL, data: String, method: String) : Boolean {
val connection = url.openConnection() as HttpURLConnection
connection.doOutput = true
connection.useCaches = false
connection.requestMethod = method
connection.connectTimeout = 5000
connection.readTimeout = 5000
connection.setRequestProperty("Connection", "Keep-Alive")
connection.setRequestProperty("Cache-Control", "no-cache")
connection.setRequestProperty("Content-Type", "application/json")
@ -244,6 +283,7 @@ private fun postJson(url: URL, data: String) : Boolean {
return sendJson(url, data, "POST")
}
// Todo: Use a simpler library function for this and handle timeout exceptions
private fun uploadFile(url: URL, file: String) : Boolean {
val boundary = "===" + System.currentTimeMillis() + "==="
val connection = url.openConnection() as HttpURLConnection
@ -251,9 +291,12 @@ private fun uploadFile(url: URL, file: String) : Boolean {
connection.doInput = true
connection.useCaches = false
connection.requestMethod = "POST"
connection.connectTimeout = 5000
connection.readTimeout = 5000
connection.setRequestProperty("Connection", "Keep-Alive")
connection.setRequestProperty("Cache-Control", "no-cache")
connection.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary)
val outStream = DataOutputStream(connection.outputStream)
outStream.writeBytes(file)
outStream.close()
@ -268,32 +311,43 @@ 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")
params.tradeWithIdentities = listOf(getRoleDir(IRSDemoRole.NodeB).resolve("identity-public"))
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.id = 1
params.dir = Paths.get("nodeB")
params.address = "localhost:31340"
params.tradeWithAddrs = listOf("localhost")
params.tradeWithIdentities = listOf(getRoleDir(IRSDemoRole.NodeA).resolve("identity-public"))
params.tradeWithIdentities = listOf(getRoleDir(IRSDemoRole.NodeA).resolve(AbstractNode.PUBLIC_IDENTITY_FILE_NAME))
params.defaultLegalName = "Bank B"
params.uploadRates = true
return params
}
private fun startNode(params : NodeParams) : Node {
private fun createNodeConfig(params: NodeParams) : NodeConfiguration {
if (!Files.exists(params.dir)) {
Files.createDirectory(params.dir)
}
val configFile = params.dir.resolve("config")
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 startNode(params : NodeParams) : Node {
val config = createNodeConfig(params)
val advertisedServices: Set<ServiceType>
val myNetAddr = HostAndPort.fromString(params.address).withDefaultPort(Node.DEFAULT_PORT)
val networkMapId = if (params.mapAddress.equals(params.address)) {
@ -302,12 +356,7 @@ private fun startNode(params : NodeParams) : Node {
null
} else {
advertisedServices = setOf(NodeInterestRates.Type)
try {
nodeInfo(params.mapAddress, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type))
} catch (e: Exception) {
null
}
nodeInfo(params.mapAddress, params.identityFile, setOf(NetworkMapService.Type, SimpleNotaryService.Type))
}
val node = logElapsedTime("Node startup") { Node(params.dir, myNetAddr, config, networkMapId,
@ -320,12 +369,8 @@ private fun startNode(params : NodeParams) : Node {
throw IllegalArgumentException("Different number of peer addresses (${params.tradeWithAddrs.size}) and identities (${params.tradeWithIdentities.size})")
}
for ((hostAndPortString, identityFile) in params.tradeWithAddrs.zip(params.tradeWithIdentities)) {
try {
val peerId = nodeInfo(hostAndPortString, identityFile)
node.services.identityService.registerIdentity(peerId.identity)
} catch (e: Exception) {
println("Could not load peer identity file \"$identityFile\".")
}
val peerId = nodeInfo(hostAndPortString, identityFile)
node.services.identityService.registerIdentity(peerId.identity)
}
return node
@ -336,7 +381,7 @@ private fun getRoleDir(role: IRSDemoRole) : Path {
IRSDemoRole.NodeA -> return Paths.get("nodeA")
IRSDemoRole.NodeB -> return Paths.get("nodeB")
else -> {
return Paths.get("nodedata")
throw IllegalArgumentException()
}
}
}
@ -348,23 +393,41 @@ private fun nodeInfo(hostAndPortString: String, identityFile: Path, advertisedSe
val party = Files.readAllBytes(path).deserialize<Party>()
return NodeInfo(ArtemisMessagingService.makeRecipient(addr), party, advertisedServices)
} catch (e: Exception) {
println("Could not find identify file $identityFile. If the file has just been created as part of starting the demo, please restart this node")
println("Could not find identify file $identityFile.")
throw e
}
}
private fun loadConfigFile(configFile: Path, defaultLegalName: String): NodeConfiguration {
if (!Files.exists(configFile)) {
private fun nodeInfo(handle: InMemoryMessagingNetwork.Handle, identityFile: Path, advertisedServices: Set<ServiceType> = emptySet()): NodeInfo {
try {
val path = identityFile
val party = Files.readAllBytes(path).deserialize<Party>()
return NodeInfo(handle, 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)
println("Default config created at $configFile.")
}
System.setProperty("config.file", configFile.toAbsolutePath().toString())
return NodeConfigurationFromConfig(ConfigFactory.load())
val config = ConfigFactory.parseFile(configFile).withFallback(ConfigFactory.load())
return NodeConfigurationFromConfig(config)
}
private fun createDefaultConfigFile(configFile: Path?, legalName: String) {
Files.write(configFile,
private fun createIdentities(params: NodeParams, nodeConf: NodeConfiguration) {
val mockNetwork = MockNetwork(false)
val node = MockNetwork.MockNode(params.dir, nodeConf, mockNetwork, null, setOf(NetworkMapService.Type, NotaryService.Type), params.id, null)
node.start()
node.stop()
}
private fun createDefaultConfigFile(configFile: File, legalName: String) {
configFile.writeBytes(
"""
myLegalName = $legalName
""".trimIndent().toByteArray())