mirror of
https://github.com/corda/corda.git
synced 2025-02-28 03:52:42 +00:00
CORPRIV-661: Allow profiles to be loaded into DemoBench.
This commit is contained in:
parent
0ae5713a47
commit
38e57d6342
@ -6,6 +6,7 @@ buildscript {
|
|||||||
ext.guava_version = '14.0.1'
|
ext.guava_version = '14.0.1'
|
||||||
ext.slf4j_version = '1.7.22'
|
ext.slf4j_version = '1.7.22'
|
||||||
ext.logback_version = '1.1.10'
|
ext.logback_version = '1.1.10'
|
||||||
|
ext.controlsfx_version = '8.40.12'
|
||||||
|
|
||||||
ext.java_home = System.properties.'java.home'
|
ext.java_home = System.properties.'java.home'
|
||||||
ext.pkg_source = "$buildDir/packagesrc"
|
ext.pkg_source = "$buildDir/packagesrc"
|
||||||
@ -52,6 +53,9 @@ dependencies {
|
|||||||
compile "no.tornado:tornadofx:$tornadofx_version"
|
compile "no.tornado:tornadofx:$tornadofx_version"
|
||||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||||
|
|
||||||
|
// Controls FX: more java FX components http://fxexperience.com/controlsfx/
|
||||||
|
compile "org.controlsfx:controlsfx:$controlsfx_version"
|
||||||
|
|
||||||
// ONLY USING THE RPC CLIENT!?
|
// ONLY USING THE RPC CLIENT!?
|
||||||
compile project(':node')
|
compile project(':node')
|
||||||
|
|
||||||
|
@ -8,11 +8,12 @@ import java.util.concurrent.Executors
|
|||||||
import kotlin.reflect.jvm.jvmName
|
import kotlin.reflect.jvm.jvmName
|
||||||
|
|
||||||
class DBViewer : AutoCloseable {
|
class DBViewer : AutoCloseable {
|
||||||
private val log = loggerFor<DBViewer>()
|
private companion object {
|
||||||
|
val log = loggerFor<DBViewer>()
|
||||||
|
}
|
||||||
|
|
||||||
private val webServer: Server
|
private val webServer: Server
|
||||||
private val pool = Executors.newCachedThreadPool()
|
private val pool = Executors.newCachedThreadPool()
|
||||||
private val t = Thread("DBViewer")
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val ws = LocalWebServer()
|
val ws = LocalWebServer()
|
||||||
@ -23,15 +24,15 @@ class DBViewer : AutoCloseable {
|
|||||||
webServer.stop()
|
webServer.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
t.run {
|
pool.submit {
|
||||||
webServer.start()
|
webServer.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun close() {
|
override fun close() {
|
||||||
webServer.shutdown()
|
log.info("Shutting down")
|
||||||
pool.shutdown()
|
pool.shutdown()
|
||||||
t.join()
|
webServer.shutdown()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun openBrowser(h2Port: Int) {
|
fun openBrowser(h2Port: Int) {
|
||||||
|
@ -4,7 +4,9 @@ import net.corda.demobench.loggerFor
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class Explorer(val explorerController: ExplorerController) : AutoCloseable {
|
class Explorer(val explorerController: ExplorerController) : AutoCloseable {
|
||||||
private val log = loggerFor<Explorer>()
|
private companion object {
|
||||||
|
val log = loggerFor<Explorer>()
|
||||||
|
}
|
||||||
|
|
||||||
private val executor = Executors.newSingleThreadExecutor()
|
private val executor = Executors.newSingleThreadExecutor()
|
||||||
private var process: Process? = null
|
private var process: Process? = null
|
||||||
@ -21,8 +23,8 @@ class Explorer(val explorerController: ExplorerController) : AutoCloseable {
|
|||||||
val p = explorerController.process(
|
val p = explorerController.process(
|
||||||
"--host=localhost",
|
"--host=localhost",
|
||||||
"--port=${config.artemisPort}",
|
"--port=${config.artemisPort}",
|
||||||
"--username=${config.user["user"]}",
|
"--username=${config.users[0]["user"]}",
|
||||||
"--password=${config.user["password"]}",
|
"--password=${config.users[0]["password"]}",
|
||||||
"--certificatesDir=${config.ssl.certificatesDirectory}",
|
"--certificatesDir=${config.ssl.certificatesDirectory}",
|
||||||
"--keyStorePassword=${config.ssl.keyStorePassword}",
|
"--keyStorePassword=${config.ssl.keyStorePassword}",
|
||||||
"--trustStorePassword=${config.ssl.trustStorePassword}")
|
"--trustStorePassword=${config.ssl.trustStorePassword}")
|
||||||
@ -51,9 +53,9 @@ class Explorer(val explorerController: ExplorerController) : AutoCloseable {
|
|||||||
process?.destroy()
|
process?.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun safeClose(c: AutoCloseable?) {
|
private fun safeClose(c: AutoCloseable) {
|
||||||
try {
|
try {
|
||||||
c?.close()
|
c.close()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.error("Failed to close stream: '{}'", e.message)
|
log.error("Failed to close stream: '{}'", e.message)
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,9 @@
|
|||||||
package net.corda.demobench.model
|
package net.corda.demobench.model
|
||||||
|
|
||||||
import com.typesafe.config.Config
|
import com.typesafe.config.*
|
||||||
import com.typesafe.config.ConfigFactory
|
|
||||||
import com.typesafe.config.ConfigValue
|
|
||||||
import com.typesafe.config.ConfigValueFactory
|
|
||||||
import net.corda.node.services.config.SSLConfiguration
|
|
||||||
import java.lang.String.join
|
import java.lang.String.join
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
|
import net.corda.node.services.config.SSLConfiguration
|
||||||
|
|
||||||
class NodeConfig(
|
class NodeConfig(
|
||||||
baseDir: Path,
|
baseDir: Path,
|
||||||
@ -15,21 +12,26 @@ class NodeConfig(
|
|||||||
val nearestCity: String,
|
val nearestCity: String,
|
||||||
val webPort: Int,
|
val webPort: Int,
|
||||||
val h2Port: Int,
|
val h2Port: Int,
|
||||||
val extraServices: List<String>
|
val extraServices: List<String>,
|
||||||
|
val users: List<Map<String, Any>> = listOf(defaultUser)
|
||||||
) : NetworkMapConfig(legalName, artemisPort) {
|
) : NetworkMapConfig(legalName, artemisPort) {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false)
|
||||||
|
|
||||||
|
val defaultUser: Map<String, Any> = mapOf(
|
||||||
|
"user" to "guest",
|
||||||
|
"password" to "letmein",
|
||||||
|
"permissions" to listOf(
|
||||||
|
"StartFlow.net.corda.flows.CashFlow",
|
||||||
|
"StartFlow.net.corda.flows.IssuerFlow\$IssuanceRequester"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val nodeDir: Path = baseDir.resolve(key)
|
val nodeDir: Path = baseDir.resolve(key)
|
||||||
val explorerDir: Path = baseDir.resolve("$key-explorer")
|
val explorerDir: Path = baseDir.resolve("$key-explorer")
|
||||||
|
|
||||||
val user: Map<String, Any> = mapOf(
|
|
||||||
"user" to "guest",
|
|
||||||
"password" to "letmein",
|
|
||||||
"permissions" to listOf(
|
|
||||||
"StartFlow.net.corda.flows.CashFlow",
|
|
||||||
"StartFlow.net.corda.flows.IssuerFlow\$IssuanceRequester"
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
val ssl: SSLConfiguration = object : SSLConfiguration {
|
val ssl: SSLConfiguration = object : SSLConfiguration {
|
||||||
override val certificatesDirectory: Path = nodeDir.resolve("certificates")
|
override val certificatesDirectory: Path = nodeDir.resolve("certificates")
|
||||||
override val trustStorePassword: String = "trustpass"
|
override val trustStorePassword: String = "trustpass"
|
||||||
@ -40,29 +42,35 @@ class NodeConfig(
|
|||||||
|
|
||||||
var state: NodeState = NodeState.STARTING
|
var state: NodeState = NodeState.STARTING
|
||||||
|
|
||||||
/*
|
|
||||||
* The configuration object depends upon the networkMap,
|
|
||||||
* which is mutable.
|
|
||||||
*/
|
|
||||||
val toFileConfig: Config
|
|
||||||
get() = ConfigFactory.empty()
|
|
||||||
.withValue("myLegalName", valueFor(legalName))
|
|
||||||
.withValue("artemisAddress", addressValueFor(artemisPort))
|
|
||||||
.withValue("nearestCity", valueFor(nearestCity))
|
|
||||||
.withValue("extraAdvertisedServiceIds", valueFor(join(",", extraServices)))
|
|
||||||
.withFallback(optional("networkMapService", networkMap, {
|
|
||||||
c, n -> c.withValue("address", addressValueFor(n.artemisPort))
|
|
||||||
.withValue("legalName", valueFor(n.legalName))
|
|
||||||
} ))
|
|
||||||
.withValue("webAddress", addressValueFor(webPort))
|
|
||||||
.withValue("rpcUsers", valueFor(listOf(user)))
|
|
||||||
.withValue("h2port", valueFor(h2Port))
|
|
||||||
.withValue("useTestClock", valueFor(true))
|
|
||||||
|
|
||||||
val isCashIssuer: Boolean = extraServices.any {
|
val isCashIssuer: Boolean = extraServices.any {
|
||||||
it.startsWith("corda.issuer.")
|
it.startsWith("corda.issuer.")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isNetworkMap(): Boolean = networkMap == null
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The configuration object depends upon the networkMap,
|
||||||
|
* which is mutable.
|
||||||
|
*/
|
||||||
|
fun toFileConfig(): Config = ConfigFactory.empty()
|
||||||
|
.withValue("myLegalName", valueFor(legalName))
|
||||||
|
.withValue("artemisAddress", addressValueFor(artemisPort))
|
||||||
|
.withValue("nearestCity", valueFor(nearestCity))
|
||||||
|
.withValue("extraAdvertisedServiceIds", valueFor(join(",", extraServices)))
|
||||||
|
.withFallback(optional("networkMapService", networkMap, {
|
||||||
|
c, n -> c.withValue("address", addressValueFor(n.artemisPort))
|
||||||
|
.withValue("legalName", valueFor(n.legalName))
|
||||||
|
} ))
|
||||||
|
.withValue("webAddress", addressValueFor(webPort))
|
||||||
|
.withValue("rpcUsers", valueFor(users))
|
||||||
|
.withValue("h2port", valueFor(h2Port))
|
||||||
|
.withValue("useTestClock", valueFor(true))
|
||||||
|
|
||||||
|
fun toText() = toFileConfig().root().render(renderOptions)
|
||||||
|
|
||||||
|
fun moveTo(baseDir: Path) = NodeConfig(
|
||||||
|
baseDir, legalName, artemisPort, nearestCity, webPort, h2Port, extraServices, users
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <T> valueFor(any: T): ConfigValue? = ConfigValueFactory.fromAnyRef(any)
|
private fun <T> valueFor(any: T): ConfigValue? = ConfigValueFactory.fromAnyRef(any)
|
||||||
|
@ -1,18 +1,17 @@
|
|||||||
package net.corda.demobench.model
|
package net.corda.demobench.model
|
||||||
|
|
||||||
import com.typesafe.config.ConfigRenderOptions
|
import java.io.IOException
|
||||||
import java.lang.management.ManagementFactory
|
import java.lang.management.ManagementFactory
|
||||||
|
import java.net.ServerSocket
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.ConcurrentHashMap
|
import java.util.concurrent.ConcurrentHashMap
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
import net.corda.demobench.pty.R3Pty
|
import net.corda.demobench.pty.R3Pty
|
||||||
import tornadofx.Controller
|
import tornadofx.Controller
|
||||||
import java.io.IOException
|
|
||||||
import java.net.ServerSocket
|
|
||||||
|
|
||||||
class NodeController : Controller() {
|
class NodeController : Controller() {
|
||||||
private companion object Data {
|
private companion object {
|
||||||
const val FIRST_PORT = 10000
|
const val FIRST_PORT = 10000
|
||||||
const val MIN_PORT = 1024
|
const val MIN_PORT = 1024
|
||||||
const val MAX_PORT = 65535
|
const val MAX_PORT = 65535
|
||||||
@ -20,9 +19,7 @@ class NodeController : Controller() {
|
|||||||
|
|
||||||
private val jvm by inject<JVMConfig>()
|
private val jvm by inject<JVMConfig>()
|
||||||
|
|
||||||
private val localDir = SimpleDateFormat("yyyyMMddHHmmss")
|
private var baseDir = baseDirFor(ManagementFactory.getRuntimeMXBean().startTime)
|
||||||
.format(Date(ManagementFactory.getRuntimeMXBean().startTime))
|
|
||||||
private val baseDir = jvm.userHome.resolve("demobench").resolve(localDir)
|
|
||||||
private val pluginDir = jvm.applicationDir.resolve("plugins")
|
private val pluginDir = jvm.applicationDir.resolve("plugins")
|
||||||
|
|
||||||
private val bankOfCorda = pluginDir.resolve("bank-of-corda.jar").toFile()
|
private val bankOfCorda = pluginDir.resolve("bank-of-corda.jar").toFile()
|
||||||
@ -30,13 +27,15 @@ class NodeController : Controller() {
|
|||||||
private val cordaPath = jvm.applicationDir.resolve("corda").resolve("corda.jar")
|
private val cordaPath = jvm.applicationDir.resolve("corda").resolve("corda.jar")
|
||||||
private val command = jvm.commandFor(cordaPath)
|
private val command = jvm.commandFor(cordaPath)
|
||||||
|
|
||||||
private val renderOptions = ConfigRenderOptions.defaults().setOriginComments(false)
|
|
||||||
|
|
||||||
private val nodes = ConcurrentHashMap<String, NodeConfig>()
|
private val nodes = ConcurrentHashMap<String, NodeConfig>()
|
||||||
private val port = AtomicInteger(FIRST_PORT)
|
private val port = AtomicInteger(FIRST_PORT)
|
||||||
|
|
||||||
private var networkMapConfig: NetworkMapConfig? = null
|
private var networkMapConfig: NetworkMapConfig? = null
|
||||||
|
|
||||||
|
val activeNodes: List<NodeConfig> get() = nodes.values.filter {
|
||||||
|
it.state == NodeState.RUNNING
|
||||||
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
log.info("Base directory: $baseDir")
|
log.info("Base directory: $baseDir")
|
||||||
log.info("Corda JAR: $cordaPath")
|
log.info("Corda JAR: $cordaPath")
|
||||||
@ -75,7 +74,7 @@ class NodeController : Controller() {
|
|||||||
val nextPort: Int get() = port.andIncrement
|
val nextPort: Int get() = port.andIncrement
|
||||||
|
|
||||||
fun isPortAvailable(port: Int): Boolean {
|
fun isPortAvailable(port: Int): Boolean {
|
||||||
if ((port >= MIN_PORT) && (port <= MAX_PORT)) {
|
if (isPortValid(port)) {
|
||||||
try {
|
try {
|
||||||
ServerSocket(port).close()
|
ServerSocket(port).close()
|
||||||
return true
|
return true
|
||||||
@ -87,6 +86,8 @@ class NodeController : Controller() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun isPortValid(port: Int): Boolean = (port >= MIN_PORT) && (port <= MAX_PORT)
|
||||||
|
|
||||||
fun keyExists(key: String) = nodes.keys.contains(key)
|
fun keyExists(key: String) = nodes.keys.contains(key)
|
||||||
|
|
||||||
fun nameExists(name: String) = keyExists(toKey(name))
|
fun nameExists(name: String) = keyExists(toKey(name))
|
||||||
@ -105,17 +106,16 @@ class NodeController : Controller() {
|
|||||||
fun runCorda(pty: R3Pty, config: NodeConfig): Boolean {
|
fun runCorda(pty: R3Pty, config: NodeConfig): Boolean {
|
||||||
val nodeDir = config.nodeDir.toFile()
|
val nodeDir = config.nodeDir.toFile()
|
||||||
|
|
||||||
if (nodeDir.mkdirs()) {
|
if (nodeDir.isDirectory || nodeDir.mkdirs()) {
|
||||||
try {
|
try {
|
||||||
// Write this node's configuration file into its working directory.
|
// Write this node's configuration file into its working directory.
|
||||||
val confFile = nodeDir.resolve("node.conf")
|
val confFile = nodeDir.resolve("node.conf")
|
||||||
val fileData = config.toFileConfig
|
confFile.writeText(config.toText())
|
||||||
confFile.writeText(fileData.root().render(renderOptions))
|
|
||||||
|
|
||||||
// Nodes cannot issue cash unless they contain the "Bank of Corda" plugin.
|
// Nodes cannot issue cash unless they contain the "Bank of Corda" plugin.
|
||||||
if (config.isCashIssuer && bankOfCorda.isFile) {
|
if (config.isCashIssuer && bankOfCorda.isFile) {
|
||||||
log.info("Installing 'Bank of Corda' plugin")
|
log.info("Installing 'Bank of Corda' plugin")
|
||||||
bankOfCorda.copyTo(nodeDir.resolve("plugins").resolve(bankOfCorda.name))
|
bankOfCorda.copyTo(nodeDir.resolve("plugins").resolve(bankOfCorda.name), overwrite=true)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the Corda node
|
// Execute the Corda node
|
||||||
@ -131,4 +131,30 @@ class NodeController : Controller() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun reset() {
|
||||||
|
baseDir = baseDirFor(System.currentTimeMillis())
|
||||||
|
log.info("Changed base directory: $baseDir")
|
||||||
|
|
||||||
|
// Wipe out any knowledge of previous nodes.
|
||||||
|
networkMapConfig = null
|
||||||
|
nodes.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun register(config: NodeConfig): Boolean {
|
||||||
|
if (nodes.putIfAbsent(config.key, config) != null) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((networkMapConfig == null) && config.isNetworkMap()) {
|
||||||
|
networkMapConfig = config
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun relocate(config: NodeConfig) = config.moveTo(baseDir)
|
||||||
|
|
||||||
|
private fun baseDirFor(time: Long) = jvm.userHome.resolve("demobench").resolve(localFor(time))
|
||||||
|
private fun localFor(time: Long) = SimpleDateFormat("yyyyMMddHHmmss").format(Date(time))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,9 @@ import net.corda.demobench.loggerFor
|
|||||||
import java.util.concurrent.Executors
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
class WebServer(val webServerController: WebServerController) : AutoCloseable {
|
class WebServer(val webServerController: WebServerController) : AutoCloseable {
|
||||||
private val log = loggerFor<WebServer>()
|
private companion object {
|
||||||
|
val log = loggerFor<WebServer>()
|
||||||
|
}
|
||||||
|
|
||||||
private val executor = Executors.newSingleThreadExecutor()
|
private val executor = Executors.newSingleThreadExecutor()
|
||||||
private var process: Process? = null
|
private var process: Process? = null
|
||||||
@ -44,9 +46,9 @@ class WebServer(val webServerController: WebServerController) : AutoCloseable {
|
|||||||
process?.destroy()
|
process?.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun safeClose(c: AutoCloseable?) {
|
private fun safeClose(c: AutoCloseable) {
|
||||||
try {
|
try {
|
||||||
c?.close()
|
c.close()
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.error("Failed to close stream: '{}'", e.message)
|
log.error("Failed to close stream: '{}'", e.message)
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
package net.corda.demobench.profile
|
||||||
|
|
||||||
|
import com.google.common.net.HostAndPort
|
||||||
|
import com.typesafe.config.Config
|
||||||
|
import com.typesafe.config.ConfigFactory
|
||||||
|
import java.nio.file.FileSystems
|
||||||
|
import java.nio.file.Files
|
||||||
|
import java.nio.file.Path
|
||||||
|
import java.util.*
|
||||||
|
import javafx.stage.FileChooser
|
||||||
|
import javafx.stage.FileChooser.ExtensionFilter
|
||||||
|
import net.corda.demobench.model.*
|
||||||
|
import tornadofx.Controller
|
||||||
|
|
||||||
|
class ProfileController : Controller() {
|
||||||
|
|
||||||
|
private val jvm by inject<JVMConfig>()
|
||||||
|
private val baseDir = jvm.userHome.resolve("demobench")
|
||||||
|
private val nodeController by inject<NodeController>()
|
||||||
|
private val serviceController by inject<ServiceController>()
|
||||||
|
private val chooser = FileChooser()
|
||||||
|
|
||||||
|
init {
|
||||||
|
chooser.initialDirectory = baseDir.toFile()
|
||||||
|
chooser.extensionFilters.add(ExtensionFilter("DemoBench profiles (*.zip)", "*.zip", "*.ZIP"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveAs() {
|
||||||
|
log.info("Save as")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun save() {
|
||||||
|
log.info("Save")
|
||||||
|
}
|
||||||
|
|
||||||
|
fun openProfile(): List<NodeConfig>? {
|
||||||
|
val chosen = chooser.showOpenDialog(null) ?: return null
|
||||||
|
log.info("Selected profile: ${chosen}")
|
||||||
|
|
||||||
|
val configs = LinkedList<NodeConfig>()
|
||||||
|
|
||||||
|
FileSystems.newFileSystem(chosen.toPath(), null).use {
|
||||||
|
fs -> fs.rootDirectories.forEach {
|
||||||
|
root -> Files.walk(root).forEach {
|
||||||
|
if ((it.nameCount == 2) && ("node.conf" == it.fileName.toString())) {
|
||||||
|
try {
|
||||||
|
configs.add(toNodeConfig(parse(it)))
|
||||||
|
} catch (e: Exception) {
|
||||||
|
log.severe("Failed to parse '$it': ${e.message}")
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configs
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toNodeConfig(config: Config): NodeConfig {
|
||||||
|
val artemisPort = config.parsePort("artemisAddress")
|
||||||
|
val webPort = config.parsePort("webAddress")
|
||||||
|
val h2Port = config.getInt("h2port")
|
||||||
|
val extraServices = config.parseExtraServices("extraAdvertisedServiceIds")
|
||||||
|
|
||||||
|
val nodeConfig = NodeConfig(
|
||||||
|
baseDir, // temporary value
|
||||||
|
config.getString("myLegalName"),
|
||||||
|
artemisPort,
|
||||||
|
config.getString("nearestCity"),
|
||||||
|
webPort,
|
||||||
|
h2Port,
|
||||||
|
extraServices,
|
||||||
|
config.getObjectList("rpcUsers").map { it.unwrapped() }.toList()
|
||||||
|
)
|
||||||
|
|
||||||
|
if (config.hasPath("networkMapService")) {
|
||||||
|
val nmap = config.getConfig("networkMapService")
|
||||||
|
nodeConfig.networkMap = NetworkMapConfig(nmap.getString("legalName"), nmap.parsePort("address"))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodeConfig
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parse(path: Path): Config = Files.newBufferedReader(path).use {
|
||||||
|
return ConfigFactory.parseReader(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Config.parsePort(path: String): Int {
|
||||||
|
val address = this.getString(path)
|
||||||
|
val port = HostAndPort.fromString(address).port
|
||||||
|
if (!nodeController.isPortValid(port)) {
|
||||||
|
throw IllegalArgumentException("Invalid port $port from '$path'.")
|
||||||
|
}
|
||||||
|
return port
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun Config.parseExtraServices(path: String): List<String> {
|
||||||
|
val services = serviceController.services.toSortedSet()
|
||||||
|
return this.getString(path).split(",").filter {
|
||||||
|
!it.isNullOrEmpty()
|
||||||
|
}.map {
|
||||||
|
if (!services.contains(it)) {
|
||||||
|
throw IllegalArgumentException("Unknown service '$it'.")
|
||||||
|
} else {
|
||||||
|
it
|
||||||
|
}
|
||||||
|
}.toList()
|
||||||
|
}
|
||||||
|
}
|
@ -13,7 +13,9 @@ import java.util.concurrent.Executors
|
|||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
class R3Pty(val name: String, settings: SettingsProvider, dimension: Dimension, val onExit: () -> Unit) : AutoCloseable {
|
class R3Pty(val name: String, settings: SettingsProvider, dimension: Dimension, val onExit: () -> Unit) : AutoCloseable {
|
||||||
private val log = loggerFor<R3Pty>()
|
private companion object {
|
||||||
|
val log = loggerFor<R3Pty>()
|
||||||
|
}
|
||||||
|
|
||||||
private val executor = Executors.newSingleThreadExecutor()
|
private val executor = Executors.newSingleThreadExecutor()
|
||||||
|
|
||||||
|
@ -9,10 +9,10 @@ import net.corda.demobench.model.NodeConfig
|
|||||||
import net.corda.node.services.messaging.CordaRPCClient
|
import net.corda.node.services.messaging.CordaRPCClient
|
||||||
|
|
||||||
class NodeRPC(config: NodeConfig, start: () -> Unit, invoke: (CordaRPCOps) -> Unit): AutoCloseable {
|
class NodeRPC(config: NodeConfig, start: () -> Unit, invoke: (CordaRPCOps) -> Unit): AutoCloseable {
|
||||||
private val log = loggerFor<NodeRPC>()
|
|
||||||
|
|
||||||
companion object Data {
|
private companion object Data {
|
||||||
private val ONE_SECOND = SECONDS.toMillis(1)
|
val log = loggerFor<NodeRPC>()
|
||||||
|
val ONE_SECOND = SECONDS.toMillis(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val rpcClient = CordaRPCClient(HostAndPort.fromParts("localhost", config.artemisPort), config.ssl)
|
private val rpcClient = CordaRPCClient(HostAndPort.fromParts("localhost", config.artemisPort), config.ssl)
|
||||||
@ -22,8 +22,8 @@ class NodeRPC(config: NodeConfig, start: () -> Unit, invoke: (CordaRPCOps) -> Un
|
|||||||
val setupTask = object : TimerTask() {
|
val setupTask = object : TimerTask() {
|
||||||
override fun run() {
|
override fun run() {
|
||||||
try {
|
try {
|
||||||
rpcClient.start(config.user.getOrElse("user") { "none" } as String,
|
rpcClient.start(config.users[0].getOrElse("user") { "none" } as String,
|
||||||
config.user.getOrElse("password") { "none" } as String)
|
config.users[0].getOrElse("password") { "none" } as String)
|
||||||
val ops = rpcClient.proxy()
|
val ops = rpcClient.proxy()
|
||||||
|
|
||||||
// Cancel the "setup" task now that we've created the RPC client.
|
// Cancel the "setup" task now that we've created the RPC client.
|
||||||
|
@ -4,17 +4,27 @@ import java.util.*
|
|||||||
import javafx.application.Platform
|
import javafx.application.Platform
|
||||||
import javafx.scene.Parent
|
import javafx.scene.Parent
|
||||||
import javafx.scene.control.Button
|
import javafx.scene.control.Button
|
||||||
|
import javafx.scene.control.MenuItem
|
||||||
import javafx.scene.control.Tab
|
import javafx.scene.control.Tab
|
||||||
import javafx.scene.control.TabPane
|
import javafx.scene.control.TabPane
|
||||||
|
import net.corda.demobench.model.NodeConfig
|
||||||
|
import net.corda.demobench.model.NodeController
|
||||||
|
import net.corda.demobench.profile.ProfileController
|
||||||
import net.corda.demobench.ui.CloseableTab
|
import net.corda.demobench.ui.CloseableTab
|
||||||
|
import org.controlsfx.dialog.ExceptionDialog
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
class DemoBenchView : View("Corda Demo Bench") {
|
class DemoBenchView : View("Corda Demo Bench") {
|
||||||
|
|
||||||
override val root by fxml<Parent>()
|
override val root by fxml<Parent>()
|
||||||
|
|
||||||
|
private val profileController by inject<ProfileController>()
|
||||||
|
private val nodeController by inject<NodeController>()
|
||||||
private val addNodeButton by fxid<Button>()
|
private val addNodeButton by fxid<Button>()
|
||||||
private val nodeTabPane by fxid<TabPane>()
|
private val nodeTabPane by fxid<TabPane>()
|
||||||
|
private val menuOpen by fxid<MenuItem>()
|
||||||
|
private val menuSave by fxid<MenuItem>()
|
||||||
|
private val menuSaveAs by fxid<MenuItem>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
importStylesheet("/net/corda/demobench/style.css")
|
importStylesheet("/net/corda/demobench/style.css")
|
||||||
@ -22,13 +32,33 @@ class DemoBenchView : View("Corda Demo Bench") {
|
|||||||
primaryStage.setOnCloseRequest {
|
primaryStage.setOnCloseRequest {
|
||||||
log.info("Exiting")
|
log.info("Exiting")
|
||||||
|
|
||||||
|
// Prevent any new NodeTabViews from being created.
|
||||||
|
addNodeButton.isDisable = true
|
||||||
|
|
||||||
closeAllTabs()
|
closeAllTabs()
|
||||||
Platform.exit()
|
Platform.exit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
menuSaveAs.setOnAction {
|
||||||
|
profileController.saveAs()
|
||||||
|
}
|
||||||
|
menuSave.setOnAction {
|
||||||
|
profileController.save()
|
||||||
|
}
|
||||||
|
menuOpen.setOnAction {
|
||||||
|
try {
|
||||||
|
val profile = profileController.openProfile()
|
||||||
|
if (profile != null) {
|
||||||
|
loadProfile(profile)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ExceptionDialog(e).apply { initOwner(root.scene.window) }.showAndWait()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
addNodeButton.setOnAction {
|
addNodeButton.setOnAction {
|
||||||
val nodeTab = createNodeTab()
|
val nodeTabView = createNodeTabView(true)
|
||||||
nodeTabPane.selectionModel.select(nodeTab)
|
nodeTabPane.selectionModel.select(nodeTabView.nodeTab)
|
||||||
|
|
||||||
// Prevent us from creating new nodes until we have created the Network Map
|
// Prevent us from creating new nodes until we have created the Network Map
|
||||||
addNodeButton.isDisable = true
|
addNodeButton.isDisable = true
|
||||||
@ -42,12 +72,22 @@ class DemoBenchView : View("Corda Demo Bench") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun createNodeTab(): CloseableTab {
|
fun createNodeTabView(showConfig: Boolean): NodeTabView {
|
||||||
val nodeTabView = find<NodeTabView>()
|
val nodeTabView = find<NodeTabView>(mapOf("showConfig" to showConfig))
|
||||||
val nodeTab = nodeTabView.nodeTab
|
nodeTabPane.tabs.add(nodeTabView.nodeTab)
|
||||||
|
return nodeTabView
|
||||||
|
}
|
||||||
|
|
||||||
nodeTabPane.tabs.add(nodeTab)
|
fun loadProfile(nodes: List<NodeConfig>) {
|
||||||
return nodeTab
|
closeAllTabs()
|
||||||
|
nodeController.reset()
|
||||||
|
|
||||||
|
nodes.forEach {
|
||||||
|
val nodeTabView = createNodeTabView(false)
|
||||||
|
nodeTabView.launch(nodeController.relocate(it))
|
||||||
|
}
|
||||||
|
|
||||||
|
enableAddNodes()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enableAddNodes() {
|
fun enableAddNodes() {
|
||||||
|
@ -15,8 +15,9 @@ class NodeTabView : Fragment() {
|
|||||||
override val root = stackpane {}
|
override val root = stackpane {}
|
||||||
|
|
||||||
private val main by inject<DemoBenchView>()
|
private val main by inject<DemoBenchView>()
|
||||||
|
private val showConfig by param<Boolean>()
|
||||||
|
|
||||||
private companion object Data {
|
private companion object {
|
||||||
val INTEGER_FORMAT = DecimalFormat()
|
val INTEGER_FORMAT = DecimalFormat()
|
||||||
val NOT_NUMBER = "[^\\d]".toRegex()
|
val NOT_NUMBER = "[^\\d]".toRegex()
|
||||||
}
|
}
|
||||||
@ -27,6 +28,8 @@ class NodeTabView : Fragment() {
|
|||||||
|
|
||||||
private val nodeTerminalView = find<NodeTerminalView>()
|
private val nodeTerminalView = find<NodeTerminalView>()
|
||||||
private val nodeConfigView = stackpane {
|
private val nodeConfigView = stackpane {
|
||||||
|
isVisible = showConfig
|
||||||
|
|
||||||
form {
|
form {
|
||||||
fieldset("Configuration") {
|
fieldset("Configuration") {
|
||||||
field("Node Name") {
|
field("Node Name") {
|
||||||
@ -179,19 +182,34 @@ class NodeTabView : Fragment() {
|
|||||||
model.h2Port.value = nodeController.nextPort
|
model.h2Port.value = nodeController.nextPort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Launches a Corda node that was configured via the form.
|
||||||
|
*/
|
||||||
fun launch() {
|
fun launch() {
|
||||||
model.commit()
|
model.commit()
|
||||||
val config = nodeController.validate(model.item)
|
val config = nodeController.validate(model.item)
|
||||||
if (config != null) {
|
if (config != null) {
|
||||||
nodeConfigView.isVisible = false
|
nodeConfigView.isVisible = false
|
||||||
nodeTab.text = config.legalName
|
launchNode(config)
|
||||||
nodeTerminalView.open(config, onExit = { onTerminalExit(config) })
|
}
|
||||||
|
}
|
||||||
|
|
||||||
nodeTab.setOnSelectionChanged {
|
/**
|
||||||
if (nodeTab.isSelected) {
|
* Launches a preconfigured Corda node, e.g. from a saved profile.
|
||||||
// Doesn't work yet
|
*/
|
||||||
nodeTerminalView.refreshTerminal()
|
fun launch(config: NodeConfig) {
|
||||||
}
|
nodeController.register(config)
|
||||||
|
launchNode(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun launchNode(config: NodeConfig) {
|
||||||
|
nodeTab.text = config.legalName
|
||||||
|
nodeTerminalView.open(config, onExit = { onTerminalExit(config) })
|
||||||
|
|
||||||
|
nodeTab.setOnSelectionChanged {
|
||||||
|
if (nodeTab.isSelected) {
|
||||||
|
// Doesn't work yet
|
||||||
|
nodeTerminalView.refreshTerminal()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,19 +2,29 @@
|
|||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.Button?>
|
||||||
|
<?import javafx.scene.control.MenuBar?>
|
||||||
|
<?import javafx.scene.control.Menu?>
|
||||||
|
<?import javafx.scene.control.MenuItem?>
|
||||||
<?import javafx.scene.control.TabPane?>
|
<?import javafx.scene.control.TabPane?>
|
||||||
<?import javafx.scene.layout.StackPane?>
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
|
||||||
<StackPane xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1">
|
<VBox xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<children>
|
<MenuBar>
|
||||||
<TabPane fx:id="nodeTabPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="444.0" minWidth="800.0" prefHeight="613.0" prefWidth="1231.0" tabClosingPolicy="UNAVAILABLE" tabMinHeight="30.0">
|
<Menu text="File">
|
||||||
<tabs>
|
<MenuItem fx:id="menuOpen" text="Open"/>
|
||||||
</tabs>
|
<MenuItem fx:id="menuSave" text="Save"/>
|
||||||
</TabPane>
|
<MenuItem fx:id="menuSaveAs" text="Save As"/>
|
||||||
<Button fx:id="addNodeButton" mnemonicParsing="false" styleClass="add-node-button" text="Add Node" StackPane.alignment="TOP_RIGHT">
|
</Menu>
|
||||||
<StackPane.margin>
|
</MenuBar>
|
||||||
<Insets right="5.0" top="5.0" />
|
<StackPane>
|
||||||
</StackPane.margin>
|
<children>
|
||||||
</Button>
|
<TabPane fx:id="nodeTabPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="444.0" minWidth="800.0" prefHeight="613.0" prefWidth="1231.0" tabClosingPolicy="UNAVAILABLE" tabMinHeight="30.0"/>
|
||||||
</children>
|
<Button fx:id="addNodeButton" mnemonicParsing="false" styleClass="add-node-button" text="Add Node" StackPane.alignment="TOP_RIGHT">
|
||||||
</StackPane>
|
<StackPane.margin>
|
||||||
|
<Insets right="5.0" top="5.0" />
|
||||||
|
</StackPane.margin>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</StackPane>
|
||||||
|
</VBox>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user