CORDA-3307: Fix for underscore variables (#5682)

* Revert "Revert "CORDA-3307 - add support for environment variables in linux (#5523)" (#5643)"

This reverts commit 03ab258fc2d0f40badd73f78c81e5ec6badf2940.

* Env variables with underscore are now validated using schema validation and checking for unknown key errors.

* Resolving comments from PR review.

* Fix for deprecated import.

* Reworked logic according to PR review.

* Resolved bad string parsing problems where the json structure could be broken if some symbols were included in the key or value.
This commit is contained in:
Stefan Iliev 2019-11-19 17:51:52 +00:00 committed by Anthony Keenan
parent c349ff719d
commit 926429647d
10 changed files with 292 additions and 95 deletions

View File

@ -88,6 +88,9 @@ Unreleased
Note that it's a responsibility of a client application to handle RPC reconnection in case this happens. Note that it's a responsibility of a client application to handle RPC reconnection in case this happens.
See :ref:`setting_jvm_args` and :ref:`memory_usage_and_tuning` for further details. See :ref:`setting_jvm_args` and :ref:`memory_usage_and_tuning` for further details.
* Environment variables and system properties can now be provided with underscore separators instead of dots. Neither are case sensitive.
See :ref:`overriding config values <corda_configuration_file_overriding_config>` for more information.
.. _changelog_v4.1: .. _changelog_v4.1:
Version 4.1 Version 4.1

View File

@ -39,6 +39,8 @@ To alter this behaviour, the ``on-unknown-config-keys`` command-line argument ca
Overriding values from node.conf Overriding values from node.conf
-------------------------------- --------------------------------
.. _corda_configuration_file_overriding_config:
Environment variables Environment variables
For example: ``${NODE_TRUST_STORE_PASSWORD}`` would be replaced by the contents of environment variable ``NODE_TRUST_STORE_PASSWORD`` (see: :ref:`hiding-sensitive-data` section). For example: ``${NODE_TRUST_STORE_PASSWORD}`` would be replaced by the contents of environment variable ``NODE_TRUST_STORE_PASSWORD`` (see: :ref:`hiding-sensitive-data` section).
@ -54,6 +56,11 @@ JVM options
.. note:: If the same field is overriden by both an environment variable and system property, the system property .. note:: If the same field is overriden by both an environment variable and system property, the system property
takes precedence. takes precedence.
.. note:: Underscores can be used in instead of dots. For example overriding the ``p2pAddress`` with an environment variable can be done
by specifying ``CORDA_P2PADDRESS=host:port``. Variables and properties are not case sensitive. Corda will warn you if a variable
prefixed with ``CORDA`` cannot be mapped to a valid property. Shadowing occurs when two properties
of the same type with the same key are defined. For example having ``CORDA_P2PADDRESS=host:port`` and ``corda_p2paddress=host1:port1``
will raise an exception on startup. This is to prevent hard to spot mistakes.
Configuration file fields Configuration file fields
------------------------- -------------------------

View File

@ -0,0 +1,110 @@
package net.corda.node
import net.corda.core.utilities.getOrThrow
import net.corda.node.logging.logFile
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.incrementalPortAllocation
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.Test
import org.junit.Assert.assertTrue
class NodeConfigParsingTests {
@Test
fun `config is overriden by underscore variable`() {
val portAllocator = incrementalPortAllocation()
val sshPort = portAllocator.nextPort()
driver(DriverParameters(
environmentVariables = mapOf("corda_sshd_port" to sshPort.toString()),
startNodesInProcess = false,
portAllocation = portAllocator)) {
val hasSsh = startNode().get()
.logFile()
.readLines()
.filter { it.contains("SSH server listening on port") }
.any { it.contains(sshPort.toString()) }
assertTrue(hasSsh)
}
}
@Test
fun `config is overriden by case insensitive underscore variable`() {
val portAllocator = incrementalPortAllocation()
val sshPort = portAllocator.nextPort()
driver(DriverParameters(
environmentVariables = mapOf("CORDA_sshd_port" to sshPort.toString()),
startNodesInProcess = false,
portAllocation = portAllocator)) {
val hasSsh = startNode().get()
.logFile()
.readLines()
.filter { it.contains("SSH server listening on port") }
.any { it.contains(sshPort.toString()) }
assertTrue(hasSsh)
}
}
@Test
fun `config is overriden by case insensitive dot variable`() {
val portAllocator = incrementalPortAllocation()
val sshPort = portAllocator.nextPort()
driver(DriverParameters(
environmentVariables = mapOf("CORDA.sshd.port" to sshPort.toString(),
"corda.devMode" to true.toString()),
startNodesInProcess = false,
portAllocation = portAllocator)) {
val hasSsh = startNode(NodeParameters()).get()
.logFile()
.readLines()
.filter { it.contains("SSH server listening on port") }
.any { it.contains(sshPort.toString()) }
assertTrue(hasSsh)
}
}
@Test
fun `shadowing is forbidden`() {
val portAllocator = incrementalPortAllocation()
val sshPort = portAllocator.nextPort()
driver(DriverParameters(
environmentVariables = mapOf(
"CORDA_sshd_port" to sshPort.toString(),
"corda.sshd.port" to sshPort.toString()),
startNodesInProcess = false,
portAllocation = portAllocator,
notarySpecs = emptyList())) {
assertThatThrownBy {
startNode().getOrThrow()
}
}
}
@Test
fun `bad keys are ignored and warned for`() {
val portAllocator = incrementalPortAllocation()
driver(DriverParameters(
environmentVariables = mapOf(
"corda_bad_key" to "2077"),
startNodesInProcess = false,
portAllocation = portAllocator,
notarySpecs = emptyList())) {
val hasWarning = startNode()
.getOrThrow()
.logFile()
.readLines()
.any {
it.contains("(property or environment variable) cannot be mapped to an existing Corda")
}
assertTrue(hasWarning)
}
}
}

View File

@ -24,4 +24,4 @@ class NodeStartupPerformanceTests {
println(times.map { it / 1_000_000.0 }) println(times.map { it / 1_000_000.0 })
} }
} }
} }

View File

@ -4,10 +4,13 @@ import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory import com.typesafe.config.ConfigFactory
import com.typesafe.config.ConfigParseOptions import com.typesafe.config.ConfigParseOptions
import net.corda.cliutils.CordaSystemUtils import net.corda.cliutils.CordaSystemUtils
import net.corda.common.configuration.parsing.internal.Configuration
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.internal.createDirectories import net.corda.core.internal.createDirectories
import net.corda.core.internal.div import net.corda.core.internal.div
import net.corda.core.internal.exists import net.corda.core.internal.exists
import net.corda.node.internal.Node
import net.corda.node.services.config.schema.v1.V1NodeConfigurationSpec
import net.corda.nodeapi.internal.DEV_CA_KEY_STORE_PASS import net.corda.nodeapi.internal.DEV_CA_KEY_STORE_PASS
import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier import net.corda.nodeapi.internal.config.FileBasedCertificateStoreSupplier
import net.corda.nodeapi.internal.config.MutualSslConfiguration import net.corda.nodeapi.internal.config.MutualSslConfiguration
@ -27,6 +30,7 @@ operator fun Config.plus(overrides: Map<String, Any?>): Config = ConfigFactory.p
object ConfigHelper { object ConfigHelper {
private const val CORDA_PROPERTY_PREFIX = "corda." private const val CORDA_PROPERTY_PREFIX = "corda."
private const val UPPERCASE_PROPERTY_PREFIX = "CORDA."
private val log = LoggerFactory.getLogger(javaClass) private val log = LoggerFactory.getLogger(javaClass)
fun loadConfig(baseDirectory: Path, fun loadConfig(baseDirectory: Path,
@ -68,10 +72,48 @@ object ConfigHelper {
} }
private fun Config.cordaEntriesOnly(): Config { private fun Config.cordaEntriesOnly(): Config {
return ConfigFactory.parseMap(toProperties() val cordaPropOccurrences = mutableSetOf<String>()
.filterKeys { (it as String).startsWith(CORDA_PROPERTY_PREFIX) } val badKeyConversions = mutableSetOf<String>()
.mapKeys { (it.key as String).removePrefix(CORDA_PROPERTY_PREFIX) }
) return ConfigFactory.parseMap(
toProperties()
.mapKeys {
var newKey = (it.key as String)
.replace('_', '.')
.replace(UPPERCASE_PROPERTY_PREFIX, CORDA_PROPERTY_PREFIX)
if (!newKey.startsWith(CORDA_PROPERTY_PREFIX)) {
return@mapKeys newKey
}
newKey = newKey.substring(CORDA_PROPERTY_PREFIX.length)
if (cordaPropOccurrences.contains(newKey))
{
throw ShadowingException(it.key.toString(), newKey)
}
cordaPropOccurrences.add(newKey)
newKey.let { key ->
val cfg = ConfigFactory.parseMap(mapOf(key to it.value))
val result = V1NodeConfigurationSpec.validate(cfg, Configuration.Validation.Options(strict = true))
val isInvalidProperty = result.errors.any { err -> err is Configuration.Validation.Error.Unknown }
if (isInvalidProperty) {
Node.printWarning(
"${it.key} (property or environment variable) cannot be mapped to an existing Corda" +
" config property and thus won't be used as a config override!" +
" It won't be passed as a config override! If that was the intention " +
" double check the spelling and ensure there is such config key.")
badKeyConversions.add(key)
}
CORDA_PROPERTY_PREFIX + key
}
}.filterKeys { it.startsWith(CORDA_PROPERTY_PREFIX) }
.mapKeys { it.key.substring(CORDA_PROPERTY_PREFIX.length) }
.filterKeys { !badKeyConversions.contains(it) })
} }
} }

View File

@ -0,0 +1,7 @@
package net.corda.node.services.config
import com.typesafe.config.ConfigException
class ShadowingException(definedProperty : String, convertedProperty : String)
: ConfigException(
"Environment variable $definedProperty is shadowing another property transformed to $convertedProperty")

View File

@ -201,7 +201,8 @@ fun <A> driver(defaultParameters: DriverParameters = DriverParameters(), dsl: Dr
inMemoryDB = defaultParameters.inMemoryDB, inMemoryDB = defaultParameters.inMemoryDB,
cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes), cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes),
djvmBootstrapSource = defaultParameters.djvmBootstrapSource, djvmBootstrapSource = defaultParameters.djvmBootstrapSource,
djvmCordaSource = defaultParameters.djvmCordaSource djvmCordaSource = defaultParameters.djvmCordaSource,
environmentVariables = defaultParameters.environmentVariables
), ),
coerce = { it }, coerce = { it },
dsl = dsl dsl = dsl
@ -261,7 +262,8 @@ data class DriverParameters(
val inMemoryDB: Boolean = true, val inMemoryDB: Boolean = true,
val cordappsForAllNodes: Collection<TestCordapp>? = null, val cordappsForAllNodes: Collection<TestCordapp>? = null,
val djvmBootstrapSource: Path? = null, val djvmBootstrapSource: Path? = null,
val djvmCordaSource: List<Path> = emptyList() val djvmCordaSource: List<Path> = emptyList(),
val environmentVariables : Map<String, String> = emptyMap()
) { ) {
constructor(cordappsForAllNodes: Collection<TestCordapp>) : this(isDebug = false, cordappsForAllNodes = cordappsForAllNodes) constructor(cordappsForAllNodes: Collection<TestCordapp>) : this(isDebug = false, cordappsForAllNodes = cordappsForAllNodes)
@ -301,7 +303,8 @@ data class DriverParameters(
// These fields have been added in v4.4 // These fields have been added in v4.4
djvmBootstrapSource = null, djvmBootstrapSource = null,
djvmCordaSource = emptyList() djvmCordaSource = emptyList(),
environmentVariables = emptyMap()
) )
constructor( constructor(
@ -420,6 +423,7 @@ data class DriverParameters(
fun withCordappsForAllNodes(cordappsForAllNodes: Collection<TestCordapp>?): DriverParameters = copy(cordappsForAllNodes = cordappsForAllNodes) fun withCordappsForAllNodes(cordappsForAllNodes: Collection<TestCordapp>?): DriverParameters = copy(cordappsForAllNodes = cordappsForAllNodes)
fun withDjvmBootstrapSource(djvmBootstrapSource: Path?): DriverParameters = copy(djvmBootstrapSource = djvmBootstrapSource) fun withDjvmBootstrapSource(djvmBootstrapSource: Path?): DriverParameters = copy(djvmBootstrapSource = djvmBootstrapSource)
fun withDjvmCordaSource(djvmCordaSource: List<Path>): DriverParameters = copy(djvmCordaSource = djvmCordaSource) fun withDjvmCordaSource(djvmCordaSource: List<Path>): DriverParameters = copy(djvmCordaSource = djvmCordaSource)
fun withEnvironmentVariables(variables : Map<String, String>): DriverParameters = copy(environmentVariables = variables)
fun copy( fun copy(
isDebug: Boolean, isDebug: Boolean,
@ -515,9 +519,9 @@ data class DriverParameters(
notaryCustomOverrides = notaryCustomOverrides, notaryCustomOverrides = notaryCustomOverrides,
inMemoryDB = inMemoryDB, inMemoryDB = inMemoryDB,
cordappsForAllNodes = cordappsForAllNodes, cordappsForAllNodes = cordappsForAllNodes,
// These fields have been added in v4.4 // These fields have been added in v4.4
djvmBootstrapSource = djvmBootstrapSource, djvmBootstrapSource = djvmBootstrapSource,
djvmCordaSource = djvmCordaSource djvmCordaSource = djvmCordaSource,
environmentVariables = environmentVariables
) )
} }

View File

@ -122,7 +122,8 @@ class DriverDSLImpl(
val inMemoryDB: Boolean, val inMemoryDB: Boolean,
val cordappsForAllNodes: Collection<TestCordappInternal>?, val cordappsForAllNodes: Collection<TestCordappInternal>?,
val djvmBootstrapSource: Path?, val djvmBootstrapSource: Path?,
val djvmCordaSource: List<Path> val djvmCordaSource: List<Path>,
val environmentVariables : Map<String, String>
) : InternalDriverDSL { ) : InternalDriverDSL {
private var _executorService: ScheduledExecutorService? = null private var _executorService: ScheduledExecutorService? = null
@ -335,9 +336,11 @@ class DriverDSLImpl(
} else { } else {
startOutOfProcessMiniNode( startOutOfProcessMiniNode(
config, config,
"initial-registration", arrayOf(
"--network-root-truststore=${rootTruststorePath.toAbsolutePath()}", "initial-registration",
"--network-root-truststore-password=$rootTruststorePassword" "--network-root-truststore=${rootTruststorePath.toAbsolutePath()}",
"--network-root-truststore-password=$rootTruststorePassword"
)
).map { config } ).map { config }
} }
} }
@ -504,7 +507,7 @@ class DriverDSLImpl(
} else { } else {
// TODO The config we use here is uses a hardocded p2p port which changes when the node is run proper // TODO The config we use here is uses a hardocded p2p port which changes when the node is run proper
// This causes two node info files to be generated. // This causes two node info files to be generated.
startOutOfProcessMiniNode(config, "generate-node-info").map { startOutOfProcessMiniNode(config, arrayOf("generate-node-info")).map {
// Once done we have to read the signed node info file that's been generated // Once done we have to read the signed node info file that's been generated
val nodeInfoFile = config.corda.baseDirectory.list { paths -> val nodeInfoFile = config.corda.baseDirectory.list { paths ->
paths.filter { it.fileName.toString().startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get() paths.filter { it.fileName.toString().startsWith(NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get()
@ -590,20 +593,20 @@ class DriverDSLImpl(
* Start the node with the given flag which is expected to start the node for some function, which once complete will * Start the node with the given flag which is expected to start the node for some function, which once complete will
* terminate the node. * terminate the node.
*/ */
@Suppress("SpreadOperator") private fun startOutOfProcessMiniNode(config: NodeConfig, extraCmdLineFlag: Array<String> = emptyArray()): CordaFuture<Unit> {
private fun startOutOfProcessMiniNode(config: NodeConfig, vararg extraCmdLineFlag: String): CordaFuture<Unit> {
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
val process = startOutOfProcessNode( val process = startOutOfProcessNode(
config, config,
quasarJarPath, quasarJarPath,
debugPort, debugPort,
bytemanJarPath, bytemanJarPath,
null, null,
systemProperties, systemProperties,
"512m", "512m",
null, null,
ZonedDateTime.now().format(DateTimeFormatter.ofPattern("HHmmss.SSS")), ZonedDateTime.now().format(DateTimeFormatter.ofPattern("HHmmss.SSS")),
*extraCmdLineFlag environmentVariables,
extraCmdLineFlag
) )
return poll(executorService, "$extraCmdLineFlag (${config.corda.myLegalName})") { return poll(executorService, "$extraCmdLineFlag (${config.corda.myLegalName})") {
@ -664,15 +667,16 @@ class DriverDSLImpl(
} else { } else {
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
val process = startOutOfProcessNode( val process = startOutOfProcessNode(
config, config,
quasarJarPath, quasarJarPath,
debugPort, debugPort,
bytemanJarPath, bytemanJarPath,
bytemanPort, bytemanPort,
systemProperties, systemProperties,
parameters.maximumHeapSize, parameters.maximumHeapSize,
parameters.logLevelOverride, parameters.logLevelOverride,
identifier identifier,
environmentVariables
) )
// Destroy the child process when the parent exits.This is needed even when `waitForAllNodesToFinish` is // Destroy the child process when the parent exits.This is needed even when `waitForAllNodesToFinish` is
@ -838,16 +842,17 @@ class DriverDSLImpl(
@Suppress("ComplexMethod", "MaxLineLength", "LongParameterList") @Suppress("ComplexMethod", "MaxLineLength", "LongParameterList")
private fun startOutOfProcessNode( private fun startOutOfProcessNode(
config: NodeConfig, config: NodeConfig,
quasarJarPath: String, quasarJarPath: String,
debugPort: Int?, debugPort: Int?,
bytemanJarPath: String?, bytemanJarPath: String?,
bytemanPort: Int?, bytemanPort: Int?,
overriddenSystemProperties: Map<String, String>, overriddenSystemProperties: Map<String, String>,
maximumHeapSize: String, maximumHeapSize: String,
logLevelOverride: String?, logLevelOverride: String?,
identifier: String, identifier: String,
vararg extraCmdLineFlag: String environmentVariables : Map<String,String>,
extraCmdLineFlag: Array<String> = emptyArray()
): Process { ): Process {
log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, " + log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, " +
"debug port is " + (debugPort ?: "not enabled") + ", " + "debug port is " + (debugPort ?: "not enabled") + ", " +
@ -892,7 +897,7 @@ class DriverDSLImpl(
"--base-directory=${config.corda.baseDirectory}", "--base-directory=${config.corda.baseDirectory}",
"--logging-level=$loggingLevel", "--logging-level=$loggingLevel",
"--no-local-shell").also { "--no-local-shell").also {
it += extraCmdLineFlag it.addAll(extraCmdLineFlag)
}.toList() }.toList()
val bytemanJvmArgs = { val bytemanJvmArgs = {
@ -920,14 +925,15 @@ class DriverDSLImpl(
} }
return ProcessUtilities.startJavaProcess( return ProcessUtilities.startJavaProcess(
className = "net.corda.node.Corda", // cannot directly get class for this, so just use string className = "net.corda.node.Corda", // cannot directly get class for this, so just use string
arguments = arguments, arguments = arguments,
jdwpPort = debugPort, jdwpPort = debugPort,
extraJvmArguments = extraJvmArguments + bytemanJvmArgs + "-Dnet.corda.node.printErrorsToStdErr=true", extraJvmArguments = extraJvmArguments + bytemanJvmArgs + "-Dnet.corda.node.printErrorsToStdErr=true",
workingDirectory = config.corda.baseDirectory, workingDirectory = config.corda.baseDirectory,
maximumHeapSize = maximumHeapSize, maximumHeapSize = maximumHeapSize,
classPath = cp, classPath = cp,
identifier = identifier identifier = identifier,
environmentVariables = environmentVariables
) )
} }
@ -1138,24 +1144,25 @@ fun <DI : DriverDSL, D : InternalDriverDSL, A> genericDriver(
val serializationEnv = setDriverSerialization() val serializationEnv = setDriverSerialization()
val driverDsl = driverDslWrapper( val driverDsl = driverDslWrapper(
DriverDSLImpl( DriverDSLImpl(
portAllocation = defaultParameters.portAllocation, portAllocation = defaultParameters.portAllocation,
debugPortAllocation = defaultParameters.debugPortAllocation, debugPortAllocation = defaultParameters.debugPortAllocation,
systemProperties = defaultParameters.systemProperties, systemProperties = defaultParameters.systemProperties,
driverDirectory = defaultParameters.driverDirectory.toAbsolutePath(), driverDirectory = defaultParameters.driverDirectory.toAbsolutePath(),
useTestClock = defaultParameters.useTestClock, useTestClock = defaultParameters.useTestClock,
isDebug = defaultParameters.isDebug, isDebug = defaultParameters.isDebug,
startNodesInProcess = defaultParameters.startNodesInProcess, startNodesInProcess = defaultParameters.startNodesInProcess,
waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish, waitForAllNodesToFinish = defaultParameters.waitForAllNodesToFinish,
extraCordappPackagesToScan = @Suppress("DEPRECATION") defaultParameters.extraCordappPackagesToScan, extraCordappPackagesToScan = @Suppress("DEPRECATION") defaultParameters.extraCordappPackagesToScan,
jmxPolicy = defaultParameters.jmxPolicy, jmxPolicy = defaultParameters.jmxPolicy,
notarySpecs = defaultParameters.notarySpecs, notarySpecs = defaultParameters.notarySpecs,
compatibilityZone = null, compatibilityZone = null,
networkParameters = defaultParameters.networkParameters, networkParameters = defaultParameters.networkParameters,
notaryCustomOverrides = defaultParameters.notaryCustomOverrides, notaryCustomOverrides = defaultParameters.notaryCustomOverrides,
inMemoryDB = defaultParameters.inMemoryDB, inMemoryDB = defaultParameters.inMemoryDB,
cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes), cordappsForAllNodes = uncheckedCast(defaultParameters.cordappsForAllNodes),
djvmBootstrapSource = defaultParameters.djvmBootstrapSource, djvmBootstrapSource = defaultParameters.djvmBootstrapSource,
djvmCordaSource = defaultParameters.djvmCordaSource djvmCordaSource = defaultParameters.djvmCordaSource,
environmentVariables = defaultParameters.environmentVariables
) )
) )
val shutdownHook = addShutdownHook(driverDsl::shutdown) val shutdownHook = addShutdownHook(driverDsl::shutdown)
@ -1252,28 +1259,30 @@ fun <A> internalDriver(
cordappsForAllNodes: Collection<TestCordappInternal>? = null, cordappsForAllNodes: Collection<TestCordappInternal>? = null,
djvmBootstrapSource: Path? = null, djvmBootstrapSource: Path? = null,
djvmCordaSource: List<Path> = emptyList(), djvmCordaSource: List<Path> = emptyList(),
environmentVariables: Map<String, String> = emptyMap(),
dsl: DriverDSLImpl.() -> A dsl: DriverDSLImpl.() -> A
): A { ): A {
return genericDriver( return genericDriver(
driverDsl = DriverDSLImpl( driverDsl = DriverDSLImpl(
portAllocation = portAllocation, portAllocation = portAllocation,
debugPortAllocation = debugPortAllocation, debugPortAllocation = debugPortAllocation,
systemProperties = systemProperties, systemProperties = systemProperties,
driverDirectory = driverDirectory.toAbsolutePath(), driverDirectory = driverDirectory.toAbsolutePath(),
useTestClock = useTestClock, useTestClock = useTestClock,
isDebug = isDebug, isDebug = isDebug,
startNodesInProcess = startNodesInProcess, startNodesInProcess = startNodesInProcess,
waitForAllNodesToFinish = waitForAllNodesToFinish, waitForAllNodesToFinish = waitForAllNodesToFinish,
extraCordappPackagesToScan = extraCordappPackagesToScan, extraCordappPackagesToScan = extraCordappPackagesToScan,
notarySpecs = notarySpecs, notarySpecs = notarySpecs,
jmxPolicy = jmxPolicy, jmxPolicy = jmxPolicy,
compatibilityZone = compatibilityZone, compatibilityZone = compatibilityZone,
networkParameters = networkParameters, networkParameters = networkParameters,
notaryCustomOverrides = notaryCustomOverrides, notaryCustomOverrides = notaryCustomOverrides,
inMemoryDB = inMemoryDB, inMemoryDB = inMemoryDB,
cordappsForAllNodes = cordappsForAllNodes, cordappsForAllNodes = cordappsForAllNodes,
djvmBootstrapSource = djvmBootstrapSource, djvmBootstrapSource = djvmBootstrapSource,
djvmCordaSource = djvmCordaSource djvmCordaSource = djvmCordaSource,
environmentVariables = environmentVariables
), ),
coerce = { it }, coerce = { it },
dsl = dsl dsl = dsl

View File

@ -5,15 +5,26 @@ import java.io.File
import java.nio.file.Path import java.nio.file.Path
object ProcessUtilities { object ProcessUtilities {
@Suppress("LongParameterList")
inline fun <reified C : Any> startJavaProcess( inline fun <reified C : Any> startJavaProcess(
arguments: List<String>, arguments: List<String>,
classPath: List<String> = defaultClassPath, classPath: List<String> = defaultClassPath,
workingDirectory: Path? = null, workingDirectory: Path? = null,
jdwpPort: Int? = null, jdwpPort: Int? = null,
extraJvmArguments: List<String> = emptyList(), extraJvmArguments: List<String> = emptyList(),
maximumHeapSize: String? = null maximumHeapSize: String? = null,
environmentVariables: Map<String, String> = emptyMap()
): Process { ): Process {
return startJavaProcess(C::class.java.name, arguments, classPath, workingDirectory, jdwpPort, extraJvmArguments, maximumHeapSize) return startJavaProcess(
C::class.java.name,
arguments,
classPath,
workingDirectory,
jdwpPort,
extraJvmArguments,
maximumHeapSize,
environmentVariables = environmentVariables
)
} }
@Suppress("LongParameterList") @Suppress("LongParameterList")
@ -25,7 +36,8 @@ object ProcessUtilities {
jdwpPort: Int? = null, jdwpPort: Int? = null,
extraJvmArguments: List<String> = emptyList(), extraJvmArguments: List<String> = emptyList(),
maximumHeapSize: String? = null, maximumHeapSize: String? = null,
identifier: String = "" identifier: String = "",
environmentVariables: Map<String,String> = emptyMap()
): Process { ): Process {
val command = mutableListOf<String>().apply { val command = mutableListOf<String>().apply {
add(javaPath) add(javaPath)
@ -38,6 +50,7 @@ object ProcessUtilities {
} }
return ProcessBuilder(command).apply { return ProcessBuilder(command).apply {
inheritIO() inheritIO()
environment().putAll(environmentVariables)
environment()["CLASSPATH"] = classPath.joinToString(File.pathSeparator) environment()["CLASSPATH"] = classPath.joinToString(File.pathSeparator)
if (workingDirectory != null) { if (workingDirectory != null) {
// An identifier may be handy if the same process started, killed and then re-started. Without the identifier // An identifier may be handy if the same process started, killed and then re-started. Without the identifier

View File

@ -124,6 +124,7 @@ fun <A> rpcDriver(
cordappsForAllNodes: Collection<TestCordappInternal>? = null, cordappsForAllNodes: Collection<TestCordappInternal>? = null,
djvmBootstrapSource: Path? = null, djvmBootstrapSource: Path? = null,
djvmCordaSource: List<Path> = emptyList(), djvmCordaSource: List<Path> = emptyList(),
environmentVariables: Map<String, String> = emptyMap(),
dsl: RPCDriverDSL.() -> A dsl: RPCDriverDSL.() -> A
): A { ): A {
return genericDriver( return genericDriver(
@ -146,7 +147,8 @@ fun <A> rpcDriver(
inMemoryDB = inMemoryDB, inMemoryDB = inMemoryDB,
cordappsForAllNodes = cordappsForAllNodes, cordappsForAllNodes = cordappsForAllNodes,
djvmBootstrapSource = djvmBootstrapSource, djvmBootstrapSource = djvmBootstrapSource,
djvmCordaSource = djvmCordaSource djvmCordaSource = djvmCordaSource,
environmentVariables = environmentVariables
), externalTrace ), externalTrace
), ),
coerce = { it }, coerce = { it },