diff --git a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt index 6d3fa042ea..cda2d6af4d 100644 --- a/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt +++ b/testing/node-driver/src/main/kotlin/net/corda/testing/node/internal/DriverDSLImpl.kt @@ -18,6 +18,7 @@ import net.corda.core.node.NetworkParameters import net.corda.core.node.NotaryInfo import net.corda.core.node.services.NetworkMapCache import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.Try import net.corda.core.utilities.contextLogger import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.millis @@ -57,6 +58,7 @@ import rx.schedulers.Schedulers import java.io.File import java.net.ConnectException import java.net.URL +import java.net.URLClassLoader import java.nio.file.Path import java.security.cert.X509Certificate import java.time.Duration @@ -122,7 +124,15 @@ class DriverDSLImpl( private val state = ThreadBox(State()) //TODO: remove this once we can bundle quasar properly. - private val quasarJarPath: String by lazy { resolveJar("co.paralleluniverse.fibers.Suspendable") } + private val quasarJarPath: String by lazy { resolveJar(".*quasar.*\\.jar$").getOrThrow() } + + private val bytemanJarPath: String? by lazy { + val maybeResolvedJar = resolveJar(".*byteman-\\d.*\\.jar$") + when (maybeResolvedJar) { + is Try.Success -> maybeResolvedJar.getOrThrow() + is Try.Failure -> null + } + } private fun NodeConfig.checkAndOverrideForInMemoryDB(): NodeConfig = this.run { if (inMemoryDB && corda.dataSourceProperties.getProperty("dataSource.url").startsWith("jdbc:h2:")) { @@ -134,14 +144,16 @@ class DriverDSLImpl( } } - private fun resolveJar(className: String): String { + private fun resolveJar(jarNamePattern: String): Try { return try { - val type = Class.forName(className) - val src = type.protectionDomain.codeSource - src.location.toPath().toString() + val cl = ClassLoader.getSystemClassLoader() + val urls = (cl as URLClassLoader).urLs + val jarPattern = jarNamePattern.toRegex() + val jarFileUrl = urls.first { jarPattern.matches(it.path) } + Try.Success(jarFileUrl.toPath().toString()) } catch (e: Exception) { - log.warn("Unable to locate JAR for class given by `$className` on classpath: ${e.message}", e) - throw e + log.warn("Unable to locate JAR `$jarNamePattern` on classpath: ${e.message}", e) + Try.Failure(e) } } @@ -178,7 +190,9 @@ class DriverDSLImpl( } } - override fun startNode(parameters: NodeParameters): CordaFuture { + override fun startNode(parameters: NodeParameters): CordaFuture = startNode(parameters, bytemanPort = null) + + override fun startNode(parameters: NodeParameters, bytemanPort: Int?): CordaFuture { val p2pAddress = portAllocation.nextHostAndPort() // TODO: Derive name from the full picked name, don't just wrap the common name val name = parameters.providedName ?: CordaX500Name("${oneOf(names).organisation}-${p2pAddress.port}", "London", "GB") @@ -193,7 +207,7 @@ class DriverDSLImpl( return registrationFuture.flatMap { networkMapAvailability.flatMap { // But starting the node proper does require the network map - startRegisteredNode(name, it, parameters, p2pAddress) + startRegisteredNode(name, it, parameters, p2pAddress, bytemanPort) } } } @@ -201,7 +215,8 @@ class DriverDSLImpl( private fun startRegisteredNode(name: CordaX500Name, localNetworkMap: LocalNetworkMap?, parameters: NodeParameters, - p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort()): CordaFuture { + p2pAddress: NetworkHostAndPort = portAllocation.nextHostAndPort(), + bytemanPort: Int? = null): CordaFuture { val rpcAddress = portAllocation.nextHostAndPort() val rpcAdminAddress = portAllocation.nextHostAndPort() val webAddress = portAllocation.nextHostAndPort() @@ -240,7 +255,7 @@ class DriverDSLImpl( allowMissingConfig = true, configOverrides = if (overrides.hasPath("devMode")) overrides else overrides + mapOf("devMode" to true) )).checkAndOverrideForInMemoryDB() - return startNodeInternal(config, webAddress, localNetworkMap, parameters) + return startNodeInternal(config, webAddress, localNetworkMap, parameters, bytemanPort) } private fun startNodeRegistration( @@ -542,6 +557,8 @@ class DriverDSLImpl( config, quasarJarPath, debugPort, + bytemanJarPath, + null, systemProperties, "512m", null, @@ -556,7 +573,8 @@ class DriverDSLImpl( private fun startNodeInternal(config: NodeConfig, webAddress: NetworkHostAndPort, localNetworkMap: LocalNetworkMap?, - parameters: NodeParameters): CordaFuture { + parameters: NodeParameters, + bytemanPort: Int?): CordaFuture { val visibilityHandle = networkVisibilityController.register(config.corda.myLegalName) val baseDirectory = config.corda.baseDirectory.createDirectories() localNetworkMap?.networkParametersCopier?.install(baseDirectory) @@ -602,7 +620,16 @@ class DriverDSLImpl( nodeFuture } else { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null - val process = startOutOfProcessNode(config, quasarJarPath, debugPort, systemProperties, parameters.maximumHeapSize, parameters.logLevelOverride) + val process = startOutOfProcessNode( + config, + quasarJarPath, + debugPort, + bytemanJarPath, + bytemanPort, + systemProperties, + parameters.maximumHeapSize, + parameters.logLevelOverride + ) // Destroy the child process when the parent exits.This is needed even when `waitForAllNodesToFinish` is // true because we don't want orphaned processes in the case that the parent process is terminated by the @@ -730,12 +757,16 @@ class DriverDSLImpl( config: NodeConfig, quasarJarPath: String, debugPort: Int?, + bytemanJarPath: String?, + bytemanPort: Int?, overriddenSystemProperties: Map, maximumHeapSize: String, logLevelOverride: String?, vararg extraCmdLineFlag: String ): Process { - log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, debug port is " + (debugPort ?: "not enabled")) + log.info("Starting out-of-process Node ${config.corda.myLegalName.organisation}, " + + "debug port is " + (debugPort ?: "not enabled") + ", " + + "byteMan: " + if (bytemanJarPath == null) "not in classpath" else "port is " + (bytemanPort ?: "not enabled")) // Write node.conf writeConfig(config.corda.baseDirectory, "node.conf", config.typesafe.toNodeOnly()) @@ -777,6 +808,17 @@ class DriverDSLImpl( it += extraCmdLineFlag }.toList() + val bytemanJvmArgs = { + val bytemanAgent = bytemanJarPath?.let { + bytemanPort?.let { + "-javaagent:$bytemanJarPath=port:$bytemanPort,listener:true" + } + } + listOfNotNull(bytemanAgent) + + if (bytemanAgent != null && debugPort != null) listOf("-Dorg.jboss.byteman.verbose=true", "-Dorg.jboss.byteman.debug=true") + else emptyList() + }.invoke() + // The following dependencies are excluded from the classpath of the created JVM, so that the environment resembles a real one as close as possible. // These are either classes that will be added as attachments to the node (i.e. samples, finance, opengamma etc.) or irrelevant testing libraries (test, corda-mock etc.). // TODO: There is pending work to fix this issue without custom blacklisting. See: https://r3-cev.atlassian.net/browse/CORDA-2164. @@ -789,7 +831,7 @@ class DriverDSLImpl( className = "net.corda.node.Corda", // cannot directly get class for this, so just use string arguments = arguments, jdwpPort = debugPort, - extraJvmArguments = extraJvmArguments, + extraJvmArguments = extraJvmArguments + bytemanJvmArgs, workingDirectory = config.corda.baseDirectory, maximumHeapSize = maximumHeapSize, classPath = cp @@ -952,6 +994,11 @@ interface InternalDriverDSL : DriverDSL { fun start() fun shutdown() + + fun startNode( + parameters: NodeParameters = NodeParameters(), + bytemanPort: Int? = null + ): CordaFuture } /**