H2 database exported via configurable port number.

This commit is contained in:
rick.parker 2016-10-04 19:01:09 +01:00
parent 31956ce78e
commit 2e3952ee1f
8 changed files with 72 additions and 11 deletions

View File

@ -75,6 +75,18 @@ To enable remote debugging of the corda process use a command line such as:
This command line will start the debugger on port 5005 and pause the process awaiting debugger attachment.
Viewing persisted state of your Node
------------------------------------
To make examining the persisted contract states of your node or the internal node database tables easier, and providing you are
using the default database configuration used for demos, you should be able to connect to the internal node database over
a JDBC connection at the URL that is output to the logs at node start up. That URL will be of the form ``jdbc:h2:tcp://<host>:<port>/node``.
The user name and password for the login are as per the node data source configuration.
The name and column layout of the internal node tables is in a state of flux and should not be relied upon to remain static
at the present time, and should certainly be treated as read-only.
.. _CordaPluginRegistry: api/com.r3corda.core.node/-corda-plugin-registry/index.html
.. _ServiceHubInternal: api/com.r3corda.node.services.api/-service-hub-internal/index.html
.. _ServiceHub: api/com.r3corda.node.services.api/-service-hub/index.html

View File

@ -11,7 +11,9 @@ as annotations and is converted to database table rows by the node automatically
node's local vault as part of a transaction.
.. note:: Presently the node includes an instance of the H2 database but any database that supports JDBC is a candidate and
the node will in the future support a range of database implementations via their JDBC drivers.
the node will in the future support a range of database implementations via their JDBC drivers. Much of the node
internal state is also persisted there. If a node is using the default H2 JDBC configuration you should be able to connect
to the H2 instance using the JDBC URL output to the logs at startup of the form ``jdbc:h2:tcp://<host>:<port>/node``
Schemas
-------

View File

@ -235,6 +235,35 @@ class Node(override val configuration: FullNodeConfiguration, networkMapAddress:
override fun makeUniquenessProvider() = PersistentUniquenessProvider()
/**
* If the node is persisting to an embedded H2 database, then expose this via TCP with a JDBC URL of the form:
* jdbc:h2:tcp://<host>:<port>/node
* with username and password as per the DataSource connection details. The key element to enabling this support is to
* ensure that you specify a JDBC connection URL of the form jdbc:h2:file: in the node config and that you include
* the H2 option AUTO_SERVER_PORT set to the port you desire to use (0 will give a dynamically allocated port number)
* but exclude the H2 option AUTO_SERVER=TRUE.
* This is not using the H2 "automatic mixed mode" directly but leans on many of the underpinnings. For more details
* on H2 URLs and configuration see: http://www.h2database.com/html/features.html#database_url
*/
override fun initialiseDatabasePersistence(insideTransaction: () -> Unit) {
val databaseUrl = configuration.dataSourceProperties.getProperty("dataSource.url")
val h2Prefix = "jdbc:h2:file:"
if (databaseUrl != null && databaseUrl.startsWith(h2Prefix)) {
val h2Port = databaseUrl.substringAfter(";AUTO_SERVER_PORT=", "").substringBefore(';')
if (h2Port.isNotBlank()) {
val databaseName = databaseUrl.removePrefix(h2Prefix).substringBefore(';')
val server = org.h2.tools.Server.createTcpServer(
"-tcpPort", h2Port,
"-tcpAllowOthers",
"-tcpDaemon",
"-key", "node", databaseName)
val url = server.start().url
log.info("H2 JDBC url is jdbc:h2:$url/node")
}
}
super.initialiseDatabasePersistence(insideTransaction)
}
override fun start(): Node {
alreadyRunningNodeCheck()
super.start()

View File

@ -6,10 +6,11 @@ keyStorePassword = "cordacadevpass"
trustStorePassword = "trustpass"
dataSourceProperties = {
dataSourceClassName = org.h2.jdbcx.JdbcDataSource
"dataSource.url" = "jdbc:h2:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;MVCC=true;MV_STORE=true;WRITE_DELAY=0"
"dataSource.url" = "jdbc:h2:file:"${basedir}"/persistence;DB_CLOSE_ON_EXIT=FALSE;LOCK_TIMEOUT=10000;MVCC=true;MV_STORE=true;WRITE_DELAY=0;AUTO_SERVER_PORT="${h2port}
"dataSource.user" = sa
"dataSource.password" = ""
}
devMode = true
certificateSigningService = "https://cordaci-netperm.corda.r3cev.com"
useHTTPS = false
useHTTPS = false
h2port = 0

View File

@ -66,7 +66,8 @@ private fun startNode(baseDirectory: Path,
"--base-directory", baseDirectory.toString(),
"--network-address", nodeAddr.toString(),
"--network-map-address", networkMapAddr.toString(),
"--api-address", apiAddr.toString())
"--api-address", apiAddr.toString(),
"--h2-port", "0")
val proc = spawn("com.r3corda.demos.IRSDemoKt", args, "IRSDemo$nodeType")
NodeApi.ensureNodeStartsOrKill(proc, apiAddr)
return proc

View File

@ -26,7 +26,8 @@ class TraderDemoTest {
"--role", "BUYER",
"--network-address", buyerAddr.toString(),
"--api-address", buyerApiAddr.toString(),
"--base-directory", baseDirectory
"--base-directory", baseDirectory,
"--h2-port", "0"
)
val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoBuyer")
NodeApi.ensureNodeStartsOrKill(proc, buyerApiAddr)
@ -42,7 +43,8 @@ class TraderDemoTest {
"--network-address", sellerAddr.toString(),
"--api-address", sellerApiAddr.toString(),
"--other-network-address", buyerAddr.toString(),
"--base-directory", baseDirectory
"--base-directory", baseDirectory,
"--h2-port", "0"
)
val proc = spawn("com.r3corda.demos.TraderDemoKt", args, "TradeDemoSeller")
assertExitOrKill(proc)

View File

@ -87,7 +87,8 @@ sealed class CliParams {
val tradeWithIdentities: List<Path>,
val uploadRates: Boolean,
val defaultLegalName: String,
val autoSetup: Boolean // Run Setup for both nodes automatically with default arguments
val autoSetup: Boolean, // Run Setup for both nodes automatically with default arguments
val h2Port: Int
) : CliParams()
/**
@ -151,6 +152,12 @@ sealed class CliParams {
IRSDemoNode.NodeB -> Node.DEFAULT_PORT + 3
}
private fun defaultH2Port(node: IRSDemoNode) =
when (node) {
IRSDemoNode.NodeA -> Node.DEFAULT_PORT + 4
IRSDemoNode.NodeB -> Node.DEFAULT_PORT + 5
}
private fun parseRunNode(options: OptionSet, node: IRSDemoNode): RunNode {
val dir = nodeDirectory(options, node)
@ -171,7 +178,8 @@ sealed class CliParams {
},
uploadRates = node == IRSDemoNode.NodeB,
defaultLegalName = legalName(node),
autoSetup = !options.has(CliParamsSpec.baseDirectoryArg) && !options.has(CliParamsSpec.fakeTradeWithIdentityFile)
autoSetup = !options.has(CliParamsSpec.baseDirectoryArg) && !options.has(CliParamsSpec.fakeTradeWithIdentityFile),
h2Port = options.valueOf(CliParamsSpec.h2PortArg.defaultsTo(defaultH2Port(node)))
)
}
@ -263,6 +271,7 @@ object CliParamsSpec {
val fakeTradeWithIdentityFile =
parser.accepts("fake-trade-with-identity-file", "Extra identities to be registered with the identity service")
.withOptionalArg()
val h2PortArg = parser.accepts("h2-port").withRequiredArg().ofType(Int::class.java)
val nonOptions = parser.nonOptions()
val help = parser.accepts("help", "Prints this help").forHelp()
}
@ -449,7 +458,8 @@ private fun getNodeConfig(cliParams: CliParams.RunNode): FullNodeConfiguration {
val configFile = cliParams.dir.resolve("config")
val configOverrides = mapOf(
"artemisAddress" to cliParams.networkAddress.toString(),
"webAddress" to cliParams.apiAddress.toString()
"webAddress" to cliParams.apiAddress.toString(),
"h2port" to cliParams.h2Port.toString()
)
return loadConfigFile(cliParams.dir, configFile, configOverrides, cliParams.defaultLegalName)
}

View File

@ -80,7 +80,7 @@ fun main(args: Array<String>) {
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 h2PortArg = parser.accepts("h2-port").withRequiredArg().ofType(Int::class.java).defaultsTo(-1)
val options = try {
parser.parse(*args)
} catch (e: Exception) {
@ -104,6 +104,9 @@ fun main(args: Array<String>) {
}
)
val apiNetAddr = HostAndPort.fromString(options.valueOf(apiNetworkAddress)).withDefaultPort(myNetAddr.port + 1)
val h2Port = if (options.valueOf(h2PortArg) < 0) {
myNetAddr.port + 2
} else options.valueOf(h2PortArg)
val baseDirectory = options.valueOf(baseDirectoryArg)!!
@ -125,7 +128,8 @@ fun main(args: Array<String>) {
val configOverrides = mapOf(
"myLegalName" to myLegalName,
"artemisAddress" to myNetAddr.toString(),
"webAddress" to apiNetAddr.toString()
"webAddress" to apiNetAddr.toString(),
"h2port" to h2Port.toString()
)
FullNodeConfiguration(NodeConfiguration.loadConfig(directory, allowMissingConfig = true, configOverrides = configOverrides))
}