mirror of
https://github.com/corda/corda.git
synced 2025-01-28 15:14:48 +00:00
CORPRIV-665: Ensure tab closes if the node exits.
This commit is contained in:
parent
6ae8a4da83
commit
b29235e7cd
@ -1,80 +0,0 @@
|
|||||||
package net.corda.demobench.pty;
|
|
||||||
|
|
||||||
import com.jediterm.terminal.TtyConnector;
|
|
||||||
import com.jediterm.terminal.ui.*;
|
|
||||||
import com.jediterm.terminal.ui.settings.SettingsProvider;
|
|
||||||
import com.pty4j.PtyProcess;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
|
|
||||||
import java.awt.*;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
|
||||||
|
|
||||||
import static java.nio.charset.StandardCharsets.UTF_8;
|
|
||||||
|
|
||||||
public class R3Pty implements AutoCloseable {
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(R3Pty.class);
|
|
||||||
|
|
||||||
private final JediTermWidget terminal;
|
|
||||||
private final String name;
|
|
||||||
|
|
||||||
public R3Pty(String name, SettingsProvider settings, Dimension dimension) {
|
|
||||||
terminal = new JediTermWidget(dimension, settings);
|
|
||||||
this.name = name;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
LOG.info("Closing terminal '{}'", name);
|
|
||||||
terminal.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
return name;
|
|
||||||
}
|
|
||||||
|
|
||||||
public JediTermWidget getTerminal() {
|
|
||||||
return terminal;
|
|
||||||
}
|
|
||||||
|
|
||||||
private TtyConnector createTtyConnector(String[] command, Map<String, String> environment, String workingDir) {
|
|
||||||
try {
|
|
||||||
PtyProcess process = PtyProcess.exec(command, environment, workingDir);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return new PtyProcessTtyConnector(name, process, UTF_8);
|
|
||||||
} catch (Exception e) {
|
|
||||||
process.destroyForcibly();
|
|
||||||
process.waitFor(30, TimeUnit.SECONDS);
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new IllegalStateException(e.getMessage(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run(String[] args, Map<String, String> envs, String workingDir) {
|
|
||||||
if (terminal.isSessionRunning()) {
|
|
||||||
throw new IllegalStateException(terminal.getSessionName() + " is already running");
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, String> environment = new HashMap<>(envs);
|
|
||||||
if (!UIUtil.isWindows) {
|
|
||||||
environment.put("TERM", "xterm");
|
|
||||||
}
|
|
||||||
|
|
||||||
TerminalSession session = terminal.createTerminalSession(createTtyConnector(args, environment, workingDir));
|
|
||||||
session.start();
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run(String[] args, Map<String, String> envs) {
|
|
||||||
run(args, envs, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void run(String... args) {
|
|
||||||
run(args, System.getenv());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -10,7 +10,7 @@ class ExplorerController : Controller() {
|
|||||||
private val explorerPath = jvm.applicationDir.resolve("explorer").resolve("node-explorer.jar")
|
private val explorerPath = jvm.applicationDir.resolve("explorer").resolve("node-explorer.jar")
|
||||||
|
|
||||||
init {
|
init {
|
||||||
log.info("Explorer JAR: " + explorerPath)
|
log.info("Explorer JAR: $explorerPath")
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun execute(cwd: Path, vararg args: String) = jvm.execute(explorerPath, cwd, *args)
|
internal fun execute(cwd: Path, vararg args: String) = jvm.execute(explorerPath, cwd, *args)
|
||||||
|
@ -8,10 +8,10 @@ class JVMConfig : Controller() {
|
|||||||
|
|
||||||
val userHome: Path = Paths.get(System.getProperty("user.home")).toAbsolutePath()
|
val userHome: Path = Paths.get(System.getProperty("user.home")).toAbsolutePath()
|
||||||
val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java")
|
val javaPath: Path = Paths.get(System.getProperty("java.home"), "bin", "java")
|
||||||
val applicationDir = Paths.get(System.getProperty("user.dir")).toAbsolutePath()
|
val applicationDir: Path = Paths.get(System.getProperty("user.dir")).toAbsolutePath()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
log.info("Java executable: " + javaPath)
|
log.info("Java executable: $javaPath")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun commandFor(jarPath: Path, vararg args: String): Array<String> {
|
fun commandFor(jarPath: Path, vararg args: String): Array<String> {
|
||||||
|
@ -38,6 +38,8 @@ class NodeConfig(
|
|||||||
|
|
||||||
var networkMap: NetworkMapConfig? = null
|
var networkMap: NetworkMapConfig? = null
|
||||||
|
|
||||||
|
var state: NodeState = NodeState.STARTING
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The configuration object depends upon the networkMap,
|
* The configuration object depends upon the networkMap,
|
||||||
* which is mutable.
|
* which is mutable.
|
||||||
|
@ -38,8 +38,8 @@ class NodeController : Controller() {
|
|||||||
private var networkMapConfig: NetworkMapConfig? = null
|
private var networkMapConfig: NetworkMapConfig? = null
|
||||||
|
|
||||||
init {
|
init {
|
||||||
log.info("Base directory: " + baseDir)
|
log.info("Base directory: $baseDir")
|
||||||
log.info("Corda JAR: " + cordaPath)
|
log.info("Corda JAR: $cordaPath")
|
||||||
}
|
}
|
||||||
|
|
||||||
fun validate(nodeData: NodeData): NodeConfig? {
|
fun validate(nodeData: NodeData): NodeConfig? {
|
||||||
@ -54,7 +54,7 @@ class NodeController : Controller() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (nodes.putIfAbsent(config.key, config) != null) {
|
if (nodes.putIfAbsent(config.key, config) != null) {
|
||||||
log.warning("Node with key '" + config.key + "' already exists.")
|
log.warning("Node with key '${config.key}' already exists.")
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,6 +64,14 @@ class NodeController : Controller() {
|
|||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun dispose(config: NodeConfig) {
|
||||||
|
config.state = NodeState.DEAD
|
||||||
|
|
||||||
|
if (config.networkMap == null) {
|
||||||
|
log.warning("Network map service (Node '${config.legalName}') has exited.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val nextPort: Int get() = port.andIncrement
|
val nextPort: Int get() = port.andIncrement
|
||||||
|
|
||||||
fun isPortAvailable(port: Int): Boolean {
|
fun isPortAvailable(port: Int): Boolean {
|
||||||
@ -90,7 +98,7 @@ class NodeController : Controller() {
|
|||||||
config.networkMap = networkMapConfig
|
config.networkMap = networkMapConfig
|
||||||
} else {
|
} else {
|
||||||
networkMapConfig = config
|
networkMapConfig = config
|
||||||
log.info("Network map provided by: " + config.legalName)
|
log.info("Network map provided by: ${config.legalName}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +120,7 @@ class NodeController : Controller() {
|
|||||||
|
|
||||||
// Execute the Corda node
|
// Execute the Corda node
|
||||||
pty.run(command, System.getenv(), nodeDir.toString())
|
pty.run(command, System.getenv(), nodeDir.toString())
|
||||||
log.info("Launched node: " + config.legalName)
|
log.info("Launched node: ${config.legalName}")
|
||||||
return true
|
return true
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
log.severe("Failed to launch Corda:" + e)
|
log.severe("Failed to launch Corda:" + e)
|
||||||
|
@ -0,0 +1,7 @@
|
|||||||
|
package net.corda.demobench.model
|
||||||
|
|
||||||
|
enum class NodeState {
|
||||||
|
STARTING,
|
||||||
|
RUNNING,
|
||||||
|
DEAD
|
||||||
|
}
|
@ -24,7 +24,7 @@ class ServiceController : Controller() {
|
|||||||
val service = it.trim()
|
val service = it.trim()
|
||||||
set.add(service)
|
set.add(service)
|
||||||
|
|
||||||
log.info("Supports: " + service)
|
log.info("Supports: $service")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return set.toList()
|
return set.toList()
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
package net.corda.demobench.pty
|
||||||
|
|
||||||
|
import com.jediterm.terminal.TtyConnector
|
||||||
|
import com.jediterm.terminal.ui.*
|
||||||
|
import com.jediterm.terminal.ui.settings.SettingsProvider
|
||||||
|
import com.pty4j.PtyProcess
|
||||||
|
import org.slf4j.LoggerFactory
|
||||||
|
|
||||||
|
import java.awt.*
|
||||||
|
import java.nio.charset.StandardCharsets.UTF_8
|
||||||
|
import java.util.*
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class R3Pty(val name: String, settings: SettingsProvider, dimension: Dimension, val onExit: () -> Unit) : AutoCloseable {
|
||||||
|
private val log = LoggerFactory.getLogger(R3Pty::class.java)
|
||||||
|
|
||||||
|
private val executor = Executors.newSingleThreadExecutor()
|
||||||
|
|
||||||
|
val terminal = JediTermWidget(dimension, settings)
|
||||||
|
|
||||||
|
override fun close() {
|
||||||
|
log.info("Closing terminal '{}'", name)
|
||||||
|
executor.shutdown()
|
||||||
|
terminal.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createTtyConnector(command: Array<String>, environment: Map<String, String>, workingDir: String?): TtyConnector {
|
||||||
|
try {
|
||||||
|
val process = PtyProcess.exec(command, environment, workingDir)
|
||||||
|
|
||||||
|
try {
|
||||||
|
return PtyProcessTtyConnector(name, process, UTF_8)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
process.destroyForcibly()
|
||||||
|
process.waitFor(30, TimeUnit.SECONDS)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
throw IllegalStateException(e.message, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun run(args: Array<String>, envs: Map<String, String>, workingDir: String?) {
|
||||||
|
if (terminal.isSessionRunning) {
|
||||||
|
throw IllegalStateException(terminal.sessionName + " is already running")
|
||||||
|
}
|
||||||
|
|
||||||
|
val environment = HashMap<String, String>(envs)
|
||||||
|
if (!UIUtil.isWindows) {
|
||||||
|
environment.put("TERM", "xterm")
|
||||||
|
}
|
||||||
|
|
||||||
|
val connector = createTtyConnector(args, environment, workingDir)
|
||||||
|
|
||||||
|
executor.submit {
|
||||||
|
val exitValue = connector.waitFor()
|
||||||
|
log.info("Terminal has exited (value={})", exitValue)
|
||||||
|
onExit()
|
||||||
|
}
|
||||||
|
|
||||||
|
val session = terminal.createTerminalSession(connector)
|
||||||
|
session.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -29,7 +29,7 @@ class NodeRPC(config: NodeConfig, start: () -> Unit, invoke: (CordaRPCOps) -> Un
|
|||||||
// Cancel the "setup" task now that we've created the RPC client.
|
// Cancel the "setup" task now that we've created the RPC client.
|
||||||
this.cancel()
|
this.cancel()
|
||||||
|
|
||||||
log.info("Node '{}' is now ready.", config.legalName)
|
// Run "start-up" task, now that the RPC client is ready.
|
||||||
start()
|
start()
|
||||||
|
|
||||||
// Schedule a new task that will refresh the display once per second.
|
// Schedule a new task that will refresh the display once per second.
|
||||||
|
@ -13,8 +13,8 @@ class DemoBenchView : View("Corda Demo Bench") {
|
|||||||
|
|
||||||
override val root by fxml<Parent>()
|
override val root by fxml<Parent>()
|
||||||
|
|
||||||
val addNodeButton by fxid<Button>()
|
private val addNodeButton by fxid<Button>()
|
||||||
val nodeTabPane by fxid<TabPane>()
|
private val nodeTabPane by fxid<TabPane>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
importStylesheet("/net/corda/demobench/style.css")
|
importStylesheet("/net/corda/demobench/style.css")
|
||||||
@ -53,4 +53,10 @@ class DemoBenchView : View("Corda Demo Bench") {
|
|||||||
fun enableAddNodes() {
|
fun enableAddNodes() {
|
||||||
addNodeButton.isDisable = false
|
addNodeButton.isDisable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun forceAtLeastOneTab() {
|
||||||
|
if (nodeTabPane.tabs.isEmpty()) {
|
||||||
|
addNodeButton.fire()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
package net.corda.demobench.views
|
package net.corda.demobench.views
|
||||||
|
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
import javafx.application.Platform
|
||||||
import javafx.scene.control.SelectionMode.MULTIPLE
|
import javafx.scene.control.SelectionMode.MULTIPLE
|
||||||
import javafx.util.converter.NumberStringConverter
|
import javafx.util.converter.NumberStringConverter
|
||||||
|
import net.corda.demobench.model.NodeConfig
|
||||||
import net.corda.demobench.model.NodeController
|
import net.corda.demobench.model.NodeController
|
||||||
import net.corda.demobench.model.NodeDataModel
|
import net.corda.demobench.model.NodeDataModel
|
||||||
import net.corda.demobench.model.ServiceController
|
import net.corda.demobench.model.ServiceController
|
||||||
@ -146,7 +148,7 @@ class NodeTabView : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
button("Create Node") {
|
button("Create Node") {
|
||||||
setOnAction() {
|
setOnAction {
|
||||||
if (model.validate()) {
|
if (model.validate()) {
|
||||||
launch()
|
launch()
|
||||||
main.enableAddNodes()
|
main.enableAddNodes()
|
||||||
@ -161,23 +163,6 @@ class NodeTabView : Fragment() {
|
|||||||
private val availableServices: List<String>
|
private val availableServices: List<String>
|
||||||
get() = if (nodeController.hasNetworkMap()) serviceController.services else serviceController.notaries
|
get() = if (nodeController.hasNetworkMap()) serviceController.services else serviceController.notaries
|
||||||
|
|
||||||
fun launch() {
|
|
||||||
model.commit()
|
|
||||||
val config = nodeController.validate(model.item)
|
|
||||||
if (config != null) {
|
|
||||||
nodeConfigView.isVisible = false
|
|
||||||
nodeTab.text = config.legalName
|
|
||||||
nodeTerminalView.open(config)
|
|
||||||
|
|
||||||
nodeTab.setOnSelectionChanged {
|
|
||||||
if (nodeTab.isSelected) {
|
|
||||||
// Doesn't work yet
|
|
||||||
nodeTerminalView.refreshTerminal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
INTEGER_FORMAT.isGroupingUsed = false
|
INTEGER_FORMAT.isGroupingUsed = false
|
||||||
|
|
||||||
@ -193,4 +178,29 @@ class NodeTabView : Fragment() {
|
|||||||
model.webPort.value = nodeController.nextPort
|
model.webPort.value = nodeController.nextPort
|
||||||
model.h2Port.value = nodeController.nextPort
|
model.h2Port.value = nodeController.nextPort
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun launch() {
|
||||||
|
model.commit()
|
||||||
|
val config = nodeController.validate(model.item)
|
||||||
|
if (config != null) {
|
||||||
|
nodeConfigView.isVisible = false
|
||||||
|
nodeTab.text = config.legalName
|
||||||
|
nodeTerminalView.open(config, onExit = { onTabClose(config) })
|
||||||
|
|
||||||
|
nodeTab.setOnSelectionChanged {
|
||||||
|
if (nodeTab.isSelected) {
|
||||||
|
// Doesn't work yet
|
||||||
|
nodeTerminalView.refreshTerminal()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onTabClose(config: NodeConfig) {
|
||||||
|
Platform.runLater {
|
||||||
|
nodeTab.requestClose()
|
||||||
|
nodeController.dispose(config)
|
||||||
|
main.forceAtLeastOneTab()
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,7 +42,7 @@ class NodeTerminalView : Fragment() {
|
|||||||
root.vgrow = Priority.ALWAYS
|
root.vgrow = Priority.ALWAYS
|
||||||
}
|
}
|
||||||
|
|
||||||
fun open(config: NodeConfig) {
|
fun open(config: NodeConfig, onExit: () -> Unit) {
|
||||||
nodeName.text = config.legalName
|
nodeName.text = config.legalName
|
||||||
p2pPort.value = config.artemisPort.toString()
|
p2pPort.value = config.artemisPort.toString()
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ class NodeTerminalView : Fragment() {
|
|||||||
root.isVisible = true
|
root.isVisible = true
|
||||||
|
|
||||||
SwingUtilities.invokeLater({
|
SwingUtilities.invokeLater({
|
||||||
val r3pty = R3Pty(config.legalName, TerminalSettingsProvider(), Dimension(160, 80))
|
val r3pty = R3Pty(config.legalName, TerminalSettingsProvider(), Dimension(160, 80), onExit)
|
||||||
pty = r3pty
|
pty = r3pty
|
||||||
|
|
||||||
swingTerminal.content = r3pty.terminal
|
swingTerminal.content = r3pty.terminal
|
||||||
@ -86,12 +86,15 @@ class NodeTerminalView : Fragment() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fun enable() {
|
fun enable(config: NodeConfig) {
|
||||||
|
config.state = NodeState.RUNNING
|
||||||
|
log.info("Node '${config.legalName}' is now ready.")
|
||||||
|
|
||||||
launchExplorerButton.isDisable = false
|
launchExplorerButton.isDisable = false
|
||||||
viewDatabaseButton.isDisable = false
|
viewDatabaseButton.isDisable = false
|
||||||
}
|
}
|
||||||
|
|
||||||
fun launchRPC(config: NodeConfig) = NodeRPC(config, start = { enable() }, invoke = { ops ->
|
fun launchRPC(config: NodeConfig) = NodeRPC(config, start = { enable(config) }, invoke = { ops ->
|
||||||
try {
|
try {
|
||||||
val verifiedTx = ops.verifiedTransactions()
|
val verifiedTx = ops.verifiedTransactions()
|
||||||
val statesInVault = ops.vaultAndUpdates()
|
val statesInVault = ops.vaultAndUpdates()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user