mirror of
https://github.com/corda/corda.git
synced 2025-02-21 09:51:57 +00:00
[CORDA-926]: Parsing NodeConfiguration will now fail if unknown properties are present. (#2484)
This commit is contained in:
parent
754b87d547
commit
b580a2ac30
@ -1,4 +1,4 @@
|
||||
gradlePluginsVersion=4.0.2
|
||||
gradlePluginsVersion=4.0.3
|
||||
kotlinVersion=1.2.20
|
||||
platformVersion=2
|
||||
guavaVersion=21.0
|
||||
|
@ -7,6 +7,12 @@ from the previous milestone release.
|
||||
UNRELEASED
|
||||
----------
|
||||
|
||||
* Parsing of ``NodeConfiguration`` will now fail if unknown configuration keys are found.
|
||||
|
||||
* The web server now has its own ``web-server.conf`` file, separate from ``node.conf``.
|
||||
|
||||
* Introduced a placeholder for custom properties within ``node.conf``; the property key is "custom".
|
||||
|
||||
* Added ``NetworkMapCache.getNodesByLegalName`` for querying nodes belonging to a distributed service such as a notary cluster
|
||||
where they all share a common identity. ``NetworkMapCache.getNodeByLegalName`` has been tightened to throw if more than
|
||||
one node with the legal name is found.
|
||||
|
@ -94,16 +94,6 @@ absolute path to the node's base directory.
|
||||
:security: Contains various nested fields controlling user authentication/authorization, in particular for RPC accesses. See
|
||||
:doc:`clientrpc` for details.
|
||||
|
||||
:webAddress: The host and port on which the webserver will listen if it is started. This is not used by the node itself.
|
||||
|
||||
.. note:: If HTTPS is enabled then the browser security checks will require that the accessing url host name is one
|
||||
of either the machine name, fully qualified machine name, or server IP address to line up with the Subject Alternative
|
||||
Names contained within the development certificates. This is addition to requiring the ``/config/dev/corda_dev_ca.cer``
|
||||
root certificate be installed as a Trusted CA.
|
||||
|
||||
.. note:: The driver will not automatically create a webserver instance, but the Cordformation will. If this field
|
||||
is present the web server will start.
|
||||
|
||||
:notary: Optional configuration object which if present configures the node to run as a notary. If part of a Raft or BFT SMaRt
|
||||
cluster then specify ``raft`` or ``bftSMaRt`` respectively as described below. If a single node notary then omit both.
|
||||
|
||||
@ -180,15 +170,16 @@ absolute path to the node's base directory.
|
||||
:attachmentCacheBound: Optionally specify how many attachments should be cached locally. Note that this includes only the key and
|
||||
metadata, the content is cached separately and can be loaded lazily. Defaults to 1024.
|
||||
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
General node configuration file for hosting the IRSDemo services:
|
||||
|
||||
.. literalinclude:: example-code/src/main/resources/example-node.conf
|
||||
:language: javascript
|
||||
:language: javascript
|
||||
|
||||
Simple notary configuration file:
|
||||
Simple notary configuration file:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
@ -202,9 +193,72 @@ Simple notary configuration file:
|
||||
address : "my-corda-node:10003"
|
||||
adminAddress : "my-corda-node:10004"
|
||||
}
|
||||
webAddress : "localhost:12347"
|
||||
notary : {
|
||||
validating : false
|
||||
}
|
||||
devMode : true
|
||||
compatibilityZoneURL : "https://cz.corda.net"
|
||||
compatibilityZoneURL : "https://cz.corda.net"
|
||||
|
||||
An example ``web-server.conf`` file is as follow:
|
||||
|
||||
.. parsed-literal::
|
||||
|
||||
myLegalName : "O=Notary Service,OU=corda,L=London,C=GB"
|
||||
keyStorePassword : "cordacadevpass"
|
||||
trustStorePassword : "trustpass"
|
||||
rpcSettings = {
|
||||
useSsl = false
|
||||
standAloneBroker = false
|
||||
address : "my-corda-node:10003"
|
||||
adminAddress : "my-corda-node:10004"
|
||||
}
|
||||
webAddress : "localhost:12347",
|
||||
rpcUsers : [{ username=user1, password=letmein, permissions=[ StartFlow.net.corda.protocols.CashProtocol ] }]
|
||||
|
||||
Fields
|
||||
------
|
||||
|
||||
The available config fields are listed below. ``baseDirectory`` is available as a substitution value, containing the absolute
|
||||
path to the node's base directory.
|
||||
|
||||
:myLegalName: The legal identity of the node acts as a human readable alias to the node's public key and several demos use
|
||||
this to lookup the NodeInfo.
|
||||
|
||||
:keyStorePassword: The password to unlock the KeyStore file (``<workspace>/certificates/sslkeystore.jks``) containing the
|
||||
node certificate and private key.
|
||||
|
||||
.. note:: This is the non-secret value for the development certificates automatically generated during the first node run.
|
||||
Longer term these keys will be managed in secure hardware devices.
|
||||
|
||||
:trustStorePassword: The password to unlock the Trust store file (``<workspace>/certificates/truststore.jks``) containing
|
||||
the Corda network root certificate. This is the non-secret value for the development certificates automatically
|
||||
generated during the first node run.
|
||||
|
||||
.. note:: Longer term these keys will be managed in secure hardware devices.
|
||||
|
||||
:rpcSettings: Options for the RPC server.
|
||||
|
||||
:useSsl: (optional) boolean, indicates whether the node should require clients to use SSL for RPC connections, defaulted to ``false``.
|
||||
:standAloneBroker: (optional) boolean, indicates whether the node will connect to a standalone broker for RPC, defaulted to ``false``.
|
||||
:address: (optional) host and port for the RPC server binding, if any.
|
||||
:adminAddress: (optional) host and port for the RPC admin binding (only required when ``useSsl`` is ``false``, because the node connects to Artemis using SSL to ensure admin privileges are not accessible outside the node).
|
||||
:ssl: (optional) SSL settings for the RPC client.
|
||||
|
||||
:keyStorePassword: password for the key store.
|
||||
:trustStorePassword: password for the trust store.
|
||||
:certificatesDirectory: directory in which the stores will be searched, unless absolute paths are provided.
|
||||
:sslKeystore: absolute path to the ssl key store, defaulted to ``certificatesDirectory / "sslkeystore.jks"``.
|
||||
:trustStoreFile: absolute path to the trust store, defaulted to ``certificatesDirectory / "truststore.jks"``.
|
||||
:trustStoreFile: absolute path to the trust store, defaulted to ``certificatesDirectory / "truststore.jks"``.
|
||||
|
||||
:webAddress: The host and port on which the webserver will listen if it is started. This is not used by the node itself.
|
||||
|
||||
:rpcUsers: A list of users who are authorised to access the RPC system. Each user in the list is a config object with the
|
||||
following fields:
|
||||
|
||||
:username: Username consisting only of word characters (a-z, A-Z, 0-9 and _)
|
||||
:password: The password
|
||||
:permissions: A list of permissions for starting flows via RPC. To give the user the permission to start the flow
|
||||
``foo.bar.FlowClass``, add the string ``StartFlow.foo.bar.FlowClass`` to the list. If the list
|
||||
contains the string ``ALL``, the user can start any flow via RPC. This value is intended for administrator
|
||||
users and for development.
|
@ -1,6 +1,4 @@
|
||||
myLegalName : "O=Notary Service,OU=corda,L=London,C=GB"
|
||||
keyStorePassword : "cordacadevpass"
|
||||
trustStorePassword : "trustpass"
|
||||
p2pAddress : "my-network-map:10000"
|
||||
webAddress : "localhost:10001"
|
||||
sshdAddress : "localhost:10002"
|
||||
p2pAddress : "my-network-map:10000"
|
@ -14,7 +14,6 @@ rpcSettings = {
|
||||
address : "my-corda-node:10003"
|
||||
adminAddress : "my-corda-node:10004"
|
||||
}
|
||||
webAddress : "localhost:10004"
|
||||
rpcUsers : [
|
||||
{ username=user1, password=letmein, permissions=[ StartFlow.net.corda.protocols.CashProtocol ] }
|
||||
]
|
||||
|
@ -1,4 +1,3 @@
|
||||
myLegalName : "O=Bank A,L=London,C=GB"
|
||||
p2pAddress : "my-corda-node:10002"
|
||||
webAddress : "localhost:10003"
|
||||
verifierType: "OutOfProcess"
|
||||
|
@ -12,7 +12,7 @@ class CashConfigDataFlowTest {
|
||||
@Test
|
||||
fun `issuable currencies are read in from node config`() {
|
||||
driver {
|
||||
val node = startNode(customOverrides = mapOf("issuableCurrencies" to listOf("EUR", "USD"))).getOrThrow()
|
||||
val node = startNode(customOverrides = mapOf("custom" to mapOf("issuableCurrencies" to listOf("EUR", "USD")))).getOrThrow()
|
||||
val config = node.rpc.startFlow(::CashConfigDataFlow).returnValue.getOrThrow()
|
||||
assertThat(config.issuableCurrencies).containsExactly(EUR, USD)
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import net.corda.finance.EUR
|
||||
import net.corda.finance.GBP
|
||||
import net.corda.finance.USD
|
||||
import net.corda.finance.flows.ConfigHolder.Companion.supportedCurrencies
|
||||
import java.io.IOException
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
|
||||
@ -35,13 +36,20 @@ class ConfigHolder(services: AppServiceHub) : SingletonSerializeAsToken() {
|
||||
.let { it.javaClass.getMethod("getConfiguration").apply { isAccessible = true }.invoke(it) }
|
||||
.let { it.javaClass.getMethod("getBaseDirectory").apply { isAccessible = true }.invoke(it)}
|
||||
.let { it.javaClass.getMethod("toString").apply { isAccessible = true }.invoke(it) as String }
|
||||
val config = (baseDirectory / "node.conf").read { ConfigFactory.parseReader(it.reader()) }
|
||||
if (config.hasPath("issuableCurrencies")) {
|
||||
issuableCurrencies = config.getStringList("issuableCurrencies").map { Currency.getInstance(it) }
|
||||
require(supportedCurrencies.containsAll(issuableCurrencies))
|
||||
} else {
|
||||
issuableCurrencies = emptyList()
|
||||
|
||||
var issuableCurrenciesValue: List<Currency>
|
||||
try {
|
||||
val config = (baseDirectory / "node.conf").read { ConfigFactory.parseReader(it.reader()) }
|
||||
if (config.hasPath("custom.issuableCurrencies")) {
|
||||
issuableCurrenciesValue = config.getStringList("custom.issuableCurrencies").map { Currency.getInstance(it) }
|
||||
require(supportedCurrencies.containsAll(issuableCurrenciesValue))
|
||||
} else {
|
||||
issuableCurrenciesValue = emptyList()
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
issuableCurrenciesValue = emptyList()
|
||||
}
|
||||
issuableCurrencies = issuableCurrenciesValue
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,8 @@ import kotlin.reflect.jvm.jvmErasure
|
||||
@Target(AnnotationTarget.PROPERTY)
|
||||
annotation class OldConfig(val value: String)
|
||||
|
||||
const val CUSTOM_NODE_PROPERTIES_ROOT = "custom"
|
||||
|
||||
// TODO Move other config parsing to use parseAs and remove this
|
||||
operator fun <T : Any> Config.getValue(receiver: Any, metadata: KProperty<*>): T {
|
||||
return getValueInternal(metadata.name, metadata.returnType)
|
||||
@ -37,9 +39,24 @@ operator fun <T : Any> Config.getValue(receiver: Any, metadata: KProperty<*>): T
|
||||
fun <T : Any> Config.parseAs(clazz: KClass<T>): T {
|
||||
require(clazz.isData) { "Only Kotlin data classes can be parsed. Offending: ${clazz.qualifiedName}" }
|
||||
val constructor = clazz.primaryConstructor!!
|
||||
val args = constructor.parameters
|
||||
.filterNot { it.isOptional && !hasPath(it.name!!) }
|
||||
.associateBy({ it }) { param ->
|
||||
val parameters = constructor.parameters
|
||||
val parameterNames = parameters.flatMap { param ->
|
||||
mutableSetOf<String>().apply {
|
||||
param.name?.let(this::add)
|
||||
clazz.memberProperties.singleOrNull { it.name == param.name }?.let { matchingProperty ->
|
||||
matchingProperty.annotations.filterIsInstance<OldConfig>().map { it.value }.forEach { this.add(it) }
|
||||
}
|
||||
}
|
||||
}
|
||||
val unknownConfigurationKeys = this.entrySet()
|
||||
.mapNotNull { it.key.split(".").firstOrNull() }
|
||||
.filterNot { it == CUSTOM_NODE_PROPERTIES_ROOT }
|
||||
.filterNot(parameterNames::contains)
|
||||
.toSortedSet()
|
||||
if (unknownConfigurationKeys.isNotEmpty()) {
|
||||
throw UnknownConfigurationKeysException.of(unknownConfigurationKeys)
|
||||
}
|
||||
val args = parameters.filterNot { it.isOptional && !hasPath(it.name!!) }.associateBy({ it }) { param ->
|
||||
// Get the matching property for this parameter
|
||||
val property = clazz.memberProperties.first { it.name == param.name }
|
||||
val path = defaultToOldPath(property)
|
||||
@ -48,6 +65,20 @@ fun <T : Any> Config.parseAs(clazz: KClass<T>): T {
|
||||
return constructor.callBy(args)
|
||||
}
|
||||
|
||||
class UnknownConfigurationKeysException private constructor(val unknownKeys: Set<String>) : IllegalArgumentException(message(unknownKeys)) {
|
||||
|
||||
init {
|
||||
require(unknownKeys.isNotEmpty()) { "Absence of unknown keys should not raise UnknownConfigurationKeysException." }
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
fun of(offendingKeys: Set<String>): UnknownConfigurationKeysException = UnknownConfigurationKeysException(offendingKeys)
|
||||
|
||||
private fun message(offendingKeys: Set<String>) = "Unknown configuration keys: ${offendingKeys.joinToString(", ", "[", "]")}."
|
||||
}
|
||||
}
|
||||
|
||||
inline fun <reified T : Any> Config.parseAs(): T = parseAs(T::class)
|
||||
|
||||
fun Config.toProperties(): Properties {
|
||||
|
@ -90,15 +90,17 @@ class NetworkBootstrapper {
|
||||
}
|
||||
|
||||
private fun generateDirectoriesIfNeeded(directory: Path) {
|
||||
val confFiles = directory.list { it.filter { it.toString().endsWith(".conf") }.toList() }
|
||||
val confFiles = directory.list { it.filter { it.toString().endsWith("_node.conf") }.toList() }
|
||||
val webServerConfFiles = directory.list { it.filter { it.toString().endsWith("_web-server.conf") }.toList() }
|
||||
if (confFiles.isEmpty()) return
|
||||
println("Node config files found in the root directory - generating node directories")
|
||||
val cordaJar = extractCordaJarTo(directory)
|
||||
for (confFile in confFiles) {
|
||||
val nodeName = confFile.fileName.toString().removeSuffix(".conf")
|
||||
val nodeName = confFile.fileName.toString().removeSuffix("_node.conf")
|
||||
println("Generating directory for $nodeName")
|
||||
val nodeDir = (directory / nodeName).createDirectories()
|
||||
confFile.moveTo(nodeDir / "node.conf", StandardCopyOption.REPLACE_EXISTING)
|
||||
webServerConfFiles.firstOrNull { directory.relativize(it).toString().removeSuffix("_web-server.conf") == nodeName }?.moveTo(nodeDir / "web-server.conf", StandardCopyOption.REPLACE_EXISTING)
|
||||
Files.copy(cordaJar, (nodeDir / "corda.jar"), StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
Files.delete(cordaJar)
|
||||
|
@ -200,6 +200,21 @@ class ConfigParsingTest {
|
||||
assertThat(DataWithCompanion(3).toConfig()).isEqualTo(config("value" to 3))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `unknown configuration keys raise exception`() {
|
||||
|
||||
// intentional typo here, parsing should throw rather than sneakily return default value
|
||||
val knownKey = "mandatory"
|
||||
val unknownKey = "optioal"
|
||||
val configuration = config(knownKey to "hello", unknownKey to "world")
|
||||
|
||||
assertThatThrownBy { configuration.parseAs<TypedConfiguration>() }.isInstanceOfSatisfying(UnknownConfigurationKeysException::class.java) { exception ->
|
||||
|
||||
assertThat(exception.unknownKeys).contains(unknownKey)
|
||||
assertThat(exception.unknownKeys).doesNotContain(knownKey)
|
||||
}
|
||||
}
|
||||
|
||||
private inline fun <reified S : SingleData<V>, reified L : ListData<V>, V : Any> testPropertyType(
|
||||
value1: V,
|
||||
value2: V,
|
||||
@ -243,6 +258,7 @@ class ConfigParsingTest {
|
||||
val values: List<T>
|
||||
}
|
||||
|
||||
data class TypedConfiguration(private val mandatory: String, private val optional: String = "optional")
|
||||
data class StringData(override val value: String) : SingleData<String>
|
||||
data class StringListData(override val values: List<String>) : ListData<String>
|
||||
data class StringSetData(val values: Set<String>)
|
||||
|
@ -82,14 +82,14 @@ class AuthDBTests : NodeBasedTest() {
|
||||
"password" to "",
|
||||
"driverClassName" to "org.h2.Driver"
|
||||
)
|
||||
),
|
||||
"options" to mapOf(
|
||||
"cache" to mapOf(
|
||||
"expireAfterSecs" to cacheExpireAfterSecs,
|
||||
"maxEntries" to 50
|
||||
)
|
||||
)
|
||||
),
|
||||
"options" to mapOf(
|
||||
"cache" to mapOf(
|
||||
"expireAfterSecs" to cacheExpireAfterSecs,
|
||||
"maxEntries" to 50
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -100,7 +100,7 @@ public class CordaCaplet extends Capsule {
|
||||
// Read JVM args from the config if specified, else leave alone.
|
||||
List<String> jvmArgs = new ArrayList<>((List<String>) super.attribute(attr));
|
||||
try {
|
||||
List<String> configJvmArgs = nodeConfig.getStringList("jvmArgs");
|
||||
List<String> configJvmArgs = nodeConfig.getStringList("custom.jvmArgs");
|
||||
jvmArgs.clear();
|
||||
jvmArgs.addAll(configJvmArgs);
|
||||
log(LOG_VERBOSE, "Configured JVM args = " + jvmArgs);
|
||||
|
@ -17,6 +17,7 @@ import net.corda.node.shell.InteractiveShell
|
||||
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
|
||||
import net.corda.node.utilities.registration.NetworkRegistrationHelper
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
import net.corda.nodeapi.internal.config.UnknownConfigurationKeysException
|
||||
import org.fusesource.jansi.Ansi
|
||||
import org.fusesource.jansi.AnsiConsole
|
||||
import org.slf4j.bridge.SLF4JBridgeHandler
|
||||
@ -86,6 +87,9 @@ open class NodeStartup(val args: Array<String>) {
|
||||
} else {
|
||||
conf0
|
||||
}
|
||||
} catch (e: UnknownConfigurationKeysException) {
|
||||
logger.error(e.message)
|
||||
return false
|
||||
} catch (e: Exception) {
|
||||
logger.error("Exception during node configuration", e)
|
||||
return false
|
||||
|
@ -150,7 +150,9 @@ data class NodeConfigurationImpl(
|
||||
override val database: DatabaseConfig = DatabaseConfig(initialiseSchema = devMode, exportHibernateJMXStatistics = devMode),
|
||||
private val transactionCacheSizeMegaBytes: Int? = null,
|
||||
private val attachmentContentCacheSizeMegaBytes: Int? = null,
|
||||
override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound
|
||||
override val attachmentCacheBound: Long = NodeConfiguration.defaultAttachmentCacheBound,
|
||||
// do not use or remove (breaks DemoBench together with rejection of unknown configuration keys during parsing)
|
||||
private val h2port: Int = 0
|
||||
) : NodeConfiguration {
|
||||
companion object {
|
||||
private val logger = loggerFor<NodeConfigurationImpl>()
|
||||
|
@ -38,7 +38,7 @@ class BankOfCordaCordform : CordformDefinition() {
|
||||
}
|
||||
node {
|
||||
name(BOC_NAME)
|
||||
extraConfig = mapOf("issuableCurrencies" to listOf("USD"))
|
||||
extraConfig = mapOf("custom" to mapOf("issuableCurrencies" to listOf("USD")))
|
||||
p2pPort(10005)
|
||||
rpcSettings {
|
||||
address("localhost:$BOC_RPC_PORT")
|
||||
|
@ -64,8 +64,8 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
notary = [validating : true]
|
||||
p2pPort 10002
|
||||
rpcSettings {
|
||||
port 10003
|
||||
adminPort 10023
|
||||
address("localhost:10003")
|
||||
adminAddress("localhost:10023")
|
||||
}
|
||||
cordapps = ["${project(":finance").group}:finance:$corda_release_version"]
|
||||
rpcUsers = rpcUsersList
|
||||
@ -75,8 +75,8 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
name "O=Bank A,L=London,C=GB"
|
||||
p2pPort 10005
|
||||
rpcSettings {
|
||||
port 10006
|
||||
adminPort 10026
|
||||
address("localhost:10006")
|
||||
adminAddress("localhost:10026")
|
||||
}
|
||||
cordapps = ["${project(":finance").group}:finance:$corda_release_version"]
|
||||
rpcUsers = rpcUsersList
|
||||
@ -86,8 +86,8 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
name "O=Bank B,L=New York,C=US"
|
||||
p2pPort 10008
|
||||
rpcSettings {
|
||||
port 10009
|
||||
adminPort 10029
|
||||
address("localhost:10009")
|
||||
adminAddress("localhost:10029")
|
||||
}
|
||||
cordapps = ["${project.group}:finance:$corda_release_version"]
|
||||
rpcUsers = rpcUsersList
|
||||
@ -97,8 +97,8 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
name "O=Regulator,L=Moscow,C=RU"
|
||||
p2pPort 10011
|
||||
rpcSettings {
|
||||
port 10012
|
||||
adminPort 10032
|
||||
address("localhost:10012")
|
||||
adminAddress("localhost:10032")
|
||||
}
|
||||
cordapps = ["${project.group}:finance:$corda_release_version"]
|
||||
cordapps = ["${project(":finance").group}:finance:$corda_release_version"]
|
||||
|
@ -70,7 +70,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
p2pPort 10002
|
||||
cordapp project(':finance')
|
||||
extraConfig = [
|
||||
jvmArgs : [ "-Xmx1g"]
|
||||
custom: [
|
||||
jvmArgs: ["-Xmx1g"]
|
||||
]
|
||||
]
|
||||
}
|
||||
node {
|
||||
@ -84,7 +86,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
cordapp project(':finance')
|
||||
rpcUsers = ext.rpcUsers
|
||||
extraConfig = [
|
||||
jvmArgs : [ "-Xmx1g"]
|
||||
custom: [
|
||||
jvmArgs: ["-Xmx1g"]
|
||||
]
|
||||
]
|
||||
}
|
||||
node {
|
||||
@ -98,7 +102,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
cordapp project(':finance')
|
||||
rpcUsers = ext.rpcUsers
|
||||
extraConfig = [
|
||||
jvmArgs : [ "-Xmx1g"]
|
||||
custom: [
|
||||
jvmArgs: ["-Xmx1g"]
|
||||
]
|
||||
]
|
||||
}
|
||||
node {
|
||||
@ -112,7 +118,9 @@ task deployNodes(type: net.corda.plugins.Cordform, dependsOn: ['jar']) {
|
||||
cordapp project(':finance')
|
||||
rpcUsers = ext.rpcUsers
|
||||
extraConfig = [
|
||||
jvmArgs : [ "-Xmx1g"]
|
||||
custom: [
|
||||
jvmArgs: ["-Xmx1g"]
|
||||
]
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import com.google.common.util.concurrent.ThreadFactoryBuilder
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.client.rpc.CordaRPCClient
|
||||
import net.corda.client.rpc.internal.createCordaRPCClientWithSsl
|
||||
import net.corda.cordform.CordformContext
|
||||
@ -229,7 +230,6 @@ class DriverDSLImpl(
|
||||
"p2pAddress" to p2pAddress.toString(),
|
||||
"rpcSettings.address" to rpcAddress.toString(),
|
||||
"rpcSettings.adminAddress" to rpcAdminAddress.toString(),
|
||||
"webAddress" to webAddress.toString(),
|
||||
"useTestClock" to useTestClock,
|
||||
"rpcUsers" to if (users.isEmpty()) defaultRpcUserList else users.map { it.toConfig().root().unwrapped() },
|
||||
"verifierType" to verifierType.name
|
||||
@ -355,13 +355,17 @@ class DriverDSLImpl(
|
||||
val webAddress = cordform.webAddress?.let { NetworkHostAndPort.parse(it) } ?: portAllocation.nextHostAndPort()
|
||||
val notary = if (cordform.notary != null) mapOf("notary" to cordform.notary) else emptyMap()
|
||||
val rpcUsers = cordform.rpcUsers
|
||||
val config = NodeConfig(ConfigHelper.loadConfig(
|
||||
|
||||
val rawConfig = cordform.config + rpcAddress + notary + mapOf(
|
||||
"rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers
|
||||
)
|
||||
val typesafe = ConfigHelper.loadConfig(
|
||||
baseDirectory = baseDirectory(name),
|
||||
allowMissingConfig = true,
|
||||
configOverrides = cordform.config + rpcAddress + notary + mapOf(
|
||||
"rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers
|
||||
)
|
||||
))
|
||||
configOverrides = rawConfig.toNodeOnly()
|
||||
)
|
||||
val cordaConfig = typesafe.parseAsNodeConfiguration()
|
||||
val config = NodeConfig(rawConfig, cordaConfig)
|
||||
return startNodeInternal(config, webAddress, null, "200m", localNetworkMap)
|
||||
}
|
||||
|
||||
@ -384,9 +388,9 @@ class DriverDSLImpl(
|
||||
|
||||
override fun startWebserver(handle: NodeHandle, maximumHeapSize: String): CordaFuture<WebserverHandle> {
|
||||
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
|
||||
val process = startWebserver(handle, debugPort, maximumHeapSize)
|
||||
val process = startWebserver(handle as NodeHandleInternal, debugPort, maximumHeapSize)
|
||||
shutdownManager.registerProcessShutdown(process)
|
||||
val webReadyFuture = addressMustBeBoundFuture(executorService, (handle as NodeHandleInternal).webAddress, process)
|
||||
val webReadyFuture = addressMustBeBoundFuture(executorService, handle.webAddress, process)
|
||||
return webReadyFuture.map { queryWebserver(handle, process) }
|
||||
}
|
||||
|
||||
@ -726,14 +730,12 @@ class DriverDSLImpl(
|
||||
* Simple holder class to capture the node configuration both as the raw [Config] object and the parsed [NodeConfiguration].
|
||||
* Keeping [Config] around is needed as the user may specify extra config options not specified in [NodeConfiguration].
|
||||
*/
|
||||
private class NodeConfig(val typesafe: Config) {
|
||||
val corda: NodeConfiguration = typesafe.parseAsNodeConfiguration().also { nodeConfiguration ->
|
||||
val errors = nodeConfiguration.validate()
|
||||
if (errors.isNotEmpty()) {
|
||||
throw IllegalStateException("Invalid node configuration. Errors where:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}")
|
||||
}
|
||||
private class NodeConfig(val typesafe: Config, val corda: NodeConfiguration = typesafe.parseAsNodeConfiguration().also { nodeConfiguration ->
|
||||
val errors = nodeConfiguration.validate()
|
||||
if (errors.isNotEmpty()) {
|
||||
throw IllegalStateException("Invalid node configuration. Errors where:${System.lineSeparator()}${errors.joinToString(System.lineSeparator())}")
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
companion object {
|
||||
internal val log = contextLogger()
|
||||
@ -771,7 +773,7 @@ class DriverDSLImpl(
|
||||
throw IllegalStateException("No quasar agent: -javaagent:lib/quasar.jar and working directory project root might fix")
|
||||
}
|
||||
// Write node.conf
|
||||
writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe)
|
||||
writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe.toNodeOnly())
|
||||
// TODO pass the version in?
|
||||
val node = InProcessNode(config.corda, MOCK_VERSION_INFO, cordappPackages).start()
|
||||
val nodeThread = thread(name = config.corda.myLegalName.organisation) {
|
||||
@ -798,7 +800,7 @@ class DriverDSLImpl(
|
||||
"debug port is " + (debugPort ?: "not enabled") + ", " +
|
||||
"jolokia monitoring port is " + (monitorPort ?: "not enabled"))
|
||||
// Write node.conf
|
||||
writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe)
|
||||
writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe.toNodeOnly())
|
||||
|
||||
val systemProperties = mutableMapOf(
|
||||
"name" to config.corda.myLegalName,
|
||||
@ -844,8 +846,9 @@ class DriverDSLImpl(
|
||||
)
|
||||
}
|
||||
|
||||
private fun startWebserver(handle: NodeHandle, debugPort: Int?, maximumHeapSize: String): Process {
|
||||
private fun startWebserver(handle: NodeHandleInternal, debugPort: Int?, maximumHeapSize: String): Process {
|
||||
val className = "net.corda.webserver.WebServer"
|
||||
writeConfig(handle.baseDirectory, "web-server.conf", handle.toWebServerConfig())
|
||||
return ProcessUtilities.startCordaProcess(
|
||||
className = className, // cannot directly get class for this, so just use string
|
||||
arguments = listOf("--base-directory", handle.baseDirectory.toString()),
|
||||
@ -860,6 +863,22 @@ class DriverDSLImpl(
|
||||
)
|
||||
}
|
||||
|
||||
private fun NodeHandleInternal.toWebServerConfig(): Config {
|
||||
|
||||
var config = ConfigFactory.empty()
|
||||
config += "webAddress" to webAddress.toString()
|
||||
config += "myLegalName" to configuration.myLegalName.toString()
|
||||
config += "rpcAddress" to configuration.rpcOptions.address!!.toString()
|
||||
config += "rpcUsers" to configuration.toConfig().getValue("rpcUsers")
|
||||
config += "useHTTPS" to useHTTPS
|
||||
config += "baseDirectory" to configuration.baseDirectory.toAbsolutePath().toString()
|
||||
config += "keyStorePassword" to configuration.keyStorePassword
|
||||
config += "trustStorePassword" to configuration.trustStorePassword
|
||||
return config
|
||||
}
|
||||
|
||||
private operator fun Config.plus(property: Pair<String, Any>) = withValue(property.first, ConfigValueFactory.fromAnyRef(property.second))
|
||||
|
||||
/**
|
||||
* Get the package of the caller to the driver so that it can be added to the list of packages the nodes will scan.
|
||||
* This makes the driver automatically pick the CorDapp module that it's run from.
|
||||
@ -1053,3 +1072,14 @@ fun writeConfig(path: Path, filename: String, config: Config) {
|
||||
val configString = config.root().render(ConfigRenderOptions.defaults())
|
||||
configString.byteInputStream().copyTo(path / filename, StandardCopyOption.REPLACE_EXISTING)
|
||||
}
|
||||
|
||||
private fun Config.toNodeOnly(): Config {
|
||||
|
||||
return if (hasPath("webAddress")) {
|
||||
withoutPath("webAddress").withoutPath("useHTTPS")
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private operator fun Config.plus(property: Pair<String, Any>) = withValue(property.first, ConfigValueFactory.fromAnyRef(property.second))
|
@ -46,17 +46,49 @@ data class NodeConfig(
|
||||
@Suppress("unused")
|
||||
private val useTestClock = true
|
||||
|
||||
private fun asConfig(): Config {
|
||||
fun nodeConf(): Config {
|
||||
|
||||
val config = toConfig()
|
||||
val basic = NodeConfigurationData(myLegalName, p2pAddress, rpcAddress, notary, h2port, rpcUsers, useTestClock, detectPublicIp).toConfig()
|
||||
val rpcSettings = empty()
|
||||
.withValue("address", ConfigValueFactory.fromAnyRef(rpcAddress.toString()))
|
||||
.withValue("adminAddress", ConfigValueFactory.fromAnyRef(rpcAdminAddress.toString()))
|
||||
.root()
|
||||
return config.withoutPath("rpcAddress").withoutPath("rpcAdminAddress").withValue("rpcSettings", rpcSettings)
|
||||
return basic.withoutPath("rpcAddress").withoutPath("rpcAdminAddress").withValue("rpcSettings", rpcSettings)
|
||||
}
|
||||
|
||||
fun toText(): String = asConfig().root().render(renderOptions)
|
||||
fun webServerConf() = WebServerConfigurationData(myLegalName, rpcAddress, webAddress, rpcUsers).asConfig()
|
||||
|
||||
fun toNodeConfText() = nodeConf().render()
|
||||
|
||||
fun toWebServerConfText() = webServerConf().render()
|
||||
|
||||
fun serialiseAsString(): String {
|
||||
|
||||
return toConfig().render()
|
||||
}
|
||||
|
||||
private fun Config.render(): String = root().render(renderOptions)
|
||||
}
|
||||
|
||||
private data class NodeConfigurationData(
|
||||
val myLegalName: CordaX500Name,
|
||||
val p2pAddress: NetworkHostAndPort,
|
||||
val rpcAddress: NetworkHostAndPort,
|
||||
val notary: NotaryService?,
|
||||
val h2port: Int,
|
||||
val rpcUsers: List<User> = listOf(NodeConfig.defaultUser),
|
||||
val useTestClock: Boolean,
|
||||
val detectPublicIp: Boolean
|
||||
)
|
||||
|
||||
private data class WebServerConfigurationData(
|
||||
val myLegalName: CordaX500Name,
|
||||
val rpcAddress: NetworkHostAndPort,
|
||||
val webAddress: NetworkHostAndPort,
|
||||
val rpcUsers: List<User>
|
||||
) {
|
||||
|
||||
fun asConfig() = toConfig()
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -121,7 +121,11 @@ class NodeController(check: atRuntime = ::checkExists) : Controller() {
|
||||
|
||||
// Write this node's configuration file into its working directory.
|
||||
val confFile = config.nodeDir / "node.conf"
|
||||
Files.write(confFile, config.nodeConfig.toText().toByteArray())
|
||||
Files.write(confFile, config.nodeConfig.toNodeConfText().toByteArray())
|
||||
|
||||
// Write this node's configuration file into its working directory.
|
||||
val webConfFile = config.nodeDir / "web-server.conf"
|
||||
Files.write(webConfFile, config.nodeConfig.toWebServerConfText().toByteArray())
|
||||
|
||||
// Execute the Corda node
|
||||
val cordaEnv = System.getenv().toMutableMap().apply {
|
||||
|
@ -58,7 +58,7 @@ class ProfileController : Controller() {
|
||||
configs.forEach { config ->
|
||||
// Write the configuration file.
|
||||
val nodeDir = fs.getPath(config.key).createDirectories()
|
||||
val file = Files.write(nodeDir / "node.conf", config.nodeConfig.toText().toByteArray(UTF_8))
|
||||
val file = Files.write(nodeDir / "node.conf", config.nodeConfig.serialiseAsString().toByteArray(UTF_8))
|
||||
log.info("Wrote: $file")
|
||||
|
||||
// Write all of the non-built-in cordapps.
|
||||
|
@ -11,7 +11,7 @@ class WebServerController : Controller() {
|
||||
log.info("Web Server JAR: $webserverPath")
|
||||
}
|
||||
|
||||
internal fun process() = jvm.processFor(webserverPath)
|
||||
internal fun process() = jvm.processFor(webserverPath, "--config-file", "web-server.conf")
|
||||
|
||||
fun webServer() = WebServer(this)
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.node.services.config.parseAsNodeConfiguration
|
||||
import net.corda.nodeapi.internal.config.User
|
||||
import net.corda.nodeapi.internal.config.toConfig
|
||||
import net.corda.webserver.WebServerConfig
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Test
|
||||
@ -28,14 +27,14 @@ class NodeConfigTest {
|
||||
legalName = myLegalName,
|
||||
p2pPort = 10001,
|
||||
rpcPort = 40002,
|
||||
rpcAdminPort = 40003,
|
||||
rpcAdminPort = 40005,
|
||||
webPort = 20001,
|
||||
h2port = 30001,
|
||||
notary = NotaryService(validating = false),
|
||||
users = listOf(user("jenny"))
|
||||
)
|
||||
|
||||
val nodeConfig = config.toConfig()
|
||||
val nodeConfig = config.nodeConf()
|
||||
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(baseDir.toString()))
|
||||
.withFallback(ConfigFactory.parseResources("reference.conf"))
|
||||
.resolve()
|
||||
@ -63,7 +62,7 @@ class NodeConfigTest {
|
||||
users = listOf(user("jenny"))
|
||||
)
|
||||
|
||||
val nodeConfig = config.toConfig()
|
||||
val nodeConfig = config.webServerConf()
|
||||
.withValue("baseDirectory", ConfigValueFactory.fromAnyRef(baseDir.toString()))
|
||||
.withFallback(ConfigFactory.parseResources("web-reference.conf"))
|
||||
.resolve()
|
||||
|
@ -70,9 +70,9 @@ class ExplorerSimulation(private val options: OptionSet) {
|
||||
val ukBankName = CordaX500Name(organisation = "UK Bank Plc", locality = "London", country = "GB")
|
||||
val usaBankName = CordaX500Name(organisation = "USA Bank Corp", locality = "New York", country = "US")
|
||||
val issuerGBP = startNode(providedName = ukBankName, rpcUsers = listOf(manager),
|
||||
customOverrides = mapOf("issuableCurrencies" to listOf("GBP")))
|
||||
customOverrides = mapOf("custom" to mapOf("issuableCurrencies" to listOf("GBP"))))
|
||||
val issuerUSD = startNode(providedName = usaBankName, rpcUsers = listOf(manager),
|
||||
customOverrides = mapOf("issuableCurrencies" to listOf("USD")))
|
||||
customOverrides = mapOf("custom" to mapOf("issuableCurrencies" to listOf("USD"))))
|
||||
|
||||
notaryNode = defaultNotaryNode.get()
|
||||
aliceNode = alice.get()
|
||||
|
@ -25,7 +25,7 @@ class ArgsParser {
|
||||
private val configFileArg = optionParser
|
||||
.accepts("config-file", "The path to the config file")
|
||||
.withRequiredArg()
|
||||
.defaultsTo("node.conf")
|
||||
.defaultsTo("web-server.conf")
|
||||
private val loggerLevel = optionParser
|
||||
.accepts("logging-level", "Enable logging at this level and higher")
|
||||
.withRequiredArg()
|
||||
|
Loading…
x
Reference in New Issue
Block a user