mirror of
https://github.com/corda/corda.git
synced 2025-01-14 16:59:52 +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")
|
||||
|
||||
init {
|
||||
log.info("Explorer JAR: " + explorerPath)
|
||||
log.info("Explorer JAR: $explorerPath")
|
||||
}
|
||||
|
||||
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 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 {
|
||||
log.info("Java executable: " + javaPath)
|
||||
log.info("Java executable: $javaPath")
|
||||
}
|
||||
|
||||
fun commandFor(jarPath: Path, vararg args: String): Array<String> {
|
||||
|
@ -38,6 +38,8 @@ class NodeConfig(
|
||||
|
||||
var networkMap: NetworkMapConfig? = null
|
||||
|
||||
var state: NodeState = NodeState.STARTING
|
||||
|
||||
/*
|
||||
* The configuration object depends upon the networkMap,
|
||||
* which is mutable.
|
||||
|
@ -38,8 +38,8 @@ class NodeController : Controller() {
|
||||
private var networkMapConfig: NetworkMapConfig? = null
|
||||
|
||||
init {
|
||||
log.info("Base directory: " + baseDir)
|
||||
log.info("Corda JAR: " + cordaPath)
|
||||
log.info("Base directory: $baseDir")
|
||||
log.info("Corda JAR: $cordaPath")
|
||||
}
|
||||
|
||||
fun validate(nodeData: NodeData): NodeConfig? {
|
||||
@ -54,7 +54,7 @@ class NodeController : Controller() {
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
@ -64,6 +64,14 @@ class NodeController : Controller() {
|
||||
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
|
||||
|
||||
fun isPortAvailable(port: Int): Boolean {
|
||||
@ -90,7 +98,7 @@ class NodeController : Controller() {
|
||||
config.networkMap = networkMapConfig
|
||||
} else {
|
||||
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
|
||||
pty.run(command, System.getenv(), nodeDir.toString())
|
||||
log.info("Launched node: " + config.legalName)
|
||||
log.info("Launched node: ${config.legalName}")
|
||||
return true
|
||||
} catch (e: Exception) {
|
||||
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()
|
||||
set.add(service)
|
||||
|
||||
log.info("Supports: " + service)
|
||||
log.info("Supports: $service")
|
||||
}
|
||||
}
|
||||
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.
|
||||
this.cancel()
|
||||
|
||||
log.info("Node '{}' is now ready.", config.legalName)
|
||||
// Run "start-up" task, now that the RPC client is ready.
|
||||
start()
|
||||
|
||||
// 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>()
|
||||
|
||||
val addNodeButton by fxid<Button>()
|
||||
val nodeTabPane by fxid<TabPane>()
|
||||
private val addNodeButton by fxid<Button>()
|
||||
private val nodeTabPane by fxid<TabPane>()
|
||||
|
||||
init {
|
||||
importStylesheet("/net/corda/demobench/style.css")
|
||||
@ -53,4 +53,10 @@ class DemoBenchView : View("Corda Demo Bench") {
|
||||
fun enableAddNodes() {
|
||||
addNodeButton.isDisable = false
|
||||
}
|
||||
|
||||
fun forceAtLeastOneTab() {
|
||||
if (nodeTabPane.tabs.isEmpty()) {
|
||||
addNodeButton.fire()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,10 @@
|
||||
package net.corda.demobench.views
|
||||
|
||||
import java.text.DecimalFormat
|
||||
import javafx.application.Platform
|
||||
import javafx.scene.control.SelectionMode.MULTIPLE
|
||||
import javafx.util.converter.NumberStringConverter
|
||||
import net.corda.demobench.model.NodeConfig
|
||||
import net.corda.demobench.model.NodeController
|
||||
import net.corda.demobench.model.NodeDataModel
|
||||
import net.corda.demobench.model.ServiceController
|
||||
@ -146,7 +148,7 @@ class NodeTabView : Fragment() {
|
||||
}
|
||||
|
||||
button("Create Node") {
|
||||
setOnAction() {
|
||||
setOnAction {
|
||||
if (model.validate()) {
|
||||
launch()
|
||||
main.enableAddNodes()
|
||||
@ -161,23 +163,6 @@ class NodeTabView : Fragment() {
|
||||
private val availableServices: List<String>
|
||||
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 {
|
||||
INTEGER_FORMAT.isGroupingUsed = false
|
||||
|
||||
@ -193,4 +178,29 @@ class NodeTabView : Fragment() {
|
||||
model.webPort.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
|
||||
}
|
||||
|
||||
fun open(config: NodeConfig) {
|
||||
fun open(config: NodeConfig, onExit: () -> Unit) {
|
||||
nodeName.text = config.legalName
|
||||
p2pPort.value = config.artemisPort.toString()
|
||||
|
||||
@ -55,7 +55,7 @@ class NodeTerminalView : Fragment() {
|
||||
root.isVisible = true
|
||||
|
||||
SwingUtilities.invokeLater({
|
||||
val r3pty = R3Pty(config.legalName, TerminalSettingsProvider(), Dimension(160, 80))
|
||||
val r3pty = R3Pty(config.legalName, TerminalSettingsProvider(), Dimension(160, 80), onExit)
|
||||
pty = r3pty
|
||||
|
||||
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
|
||||
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 {
|
||||
val verifiedTx = ops.verifiedTransactions()
|
||||
val statesInVault = ops.vaultAndUpdates()
|
||||
|
Loading…
Reference in New Issue
Block a user