CORDA-2676: Allow more Network Bootstrapper code to be unloaded from JVM.

This commit is contained in:
Chris Rankin
2019-02-27 14:41:09 +00:00
committed by Mike Hearn
parent fa2cd907c5
commit 8306b3f708
2 changed files with 67 additions and 51 deletions

View File

@ -29,7 +29,7 @@ import net.corda.serialization.internal.SerializationFactoryImpl
import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme import net.corda.serialization.internal.amqp.AbstractAMQPSerializationScheme
import net.corda.serialization.internal.amqp.amqpMagic import net.corda.serialization.internal.amqp.amqpMagic
import java.io.File import java.io.File
import java.io.InputStream import java.net.URL
import java.nio.file.FileAlreadyExistsException import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.StandardCopyOption.REPLACE_EXISTING import java.nio.file.StandardCopyOption.REPLACE_EXISTING
@ -53,14 +53,14 @@ import kotlin.streams.toList
class NetworkBootstrapper class NetworkBootstrapper
@VisibleForTesting @VisibleForTesting
internal constructor(private val initSerEnv: Boolean, internal constructor(private val initSerEnv: Boolean,
private val embeddedCordaJar: () -> InputStream, private val embeddedCordaJar: () -> URL,
private val nodeInfosGenerator: (List<Path>) -> List<Path>, private val nodeInfosGenerator: (List<Path>) -> List<Path>,
private val contractsJarConverter: (Path) -> ContractsJar) : NetworkBootstrapperWithOverridableParameters { private val contractsJarConverter: (Path) -> ContractsJar) : NetworkBootstrapperWithOverridableParameters {
constructor() : this( constructor() : this(
initSerEnv = true, initSerEnv = true,
embeddedCordaJar = Companion::extractEmbeddedCordaJar, embeddedCordaJar = ::extractEmbeddedCordaJar,
nodeInfosGenerator = Companion::generateNodeInfos, nodeInfosGenerator = ::generateNodeInfos,
contractsJarConverter = ::ContractsJarFile contractsJarConverter = ::ContractsJarFile
) )
@ -77,8 +77,8 @@ internal constructor(private val initSerEnv: Boolean,
private val jarsThatArentCordapps = setOf("corda.jar", "runnodes.jar") private val jarsThatArentCordapps = setOf("corda.jar", "runnodes.jar")
private fun extractEmbeddedCordaJar(): InputStream { private fun extractEmbeddedCordaJar(): URL {
return Thread.currentThread().contextClassLoader.getResourceAsStream("corda.jar") return Thread.currentThread().contextClassLoader.getResource("corda.jar")
} }
private fun generateNodeInfos(nodeDirs: List<Path>): List<Path> { private fun generateNodeInfos(nodeDirs: List<Path>): List<Path> {
@ -106,6 +106,7 @@ internal constructor(private val initSerEnv: Boolean,
.redirectOutput(nodeInfoGenFile) .redirectOutput(nodeInfoGenFile)
.apply { environment()["CAPSULE_CACHE_DIR"] = "../.cache" } .apply { environment()["CAPSULE_CACHE_DIR"] = "../.cache" }
.start() .start()
try {
if (!process.waitFor(3, TimeUnit.MINUTES)) { if (!process.waitFor(3, TimeUnit.MINUTES)) {
process.destroyForcibly() process.destroyForcibly()
printNodeInfoGenLogToConsole(nodeInfoGenFile) printNodeInfoGenLogToConsole(nodeInfoGenFile)
@ -114,6 +115,11 @@ internal constructor(private val initSerEnv: Boolean,
return nodeDir.list { paths -> return nodeDir.list { paths ->
paths.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get() paths.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get()
} }
} catch (e: InterruptedException) {
// Don't leave this process dangling if the thread is interrupted.
process.destroyForcibly()
throw e
}
} }
private fun printNodeInfoGenLogToConsole(nodeInfoGenFile: File, check: (() -> Boolean) = { true }) { private fun printNodeInfoGenLogToConsole(nodeInfoGenFile: File, check: (() -> Boolean) = { true }) {
@ -257,19 +263,23 @@ internal constructor(private val initSerEnv: Boolean,
} }
} }
private fun Path.listEndingWith(suffix: String): List<Path> {
return list { file -> file.filter { it.toString().endsWith(suffix) }.toList() }
}
private fun createNodeDirectoriesIfNeeded(directory: Path, fromCordform: Boolean): Boolean { private fun createNodeDirectoriesIfNeeded(directory: Path, fromCordform: Boolean): Boolean {
var networkAlreadyExists = false var networkAlreadyExists = false
val cordaJar = directory / "corda.jar" val cordaJar = directory / "corda.jar"
var usingEmbedded = false var usingEmbedded = false
if (!cordaJar.exists()) { if (!cordaJar.exists()) {
embeddedCordaJar().use { it.copyTo(cordaJar) } embeddedCordaJar().openStream().use { it.copyTo(cordaJar) }
usingEmbedded = true usingEmbedded = true
} else if (!fromCordform) { } else if (!fromCordform) {
println("Using corda.jar in root directory") println("Using corda.jar in root directory")
} }
val confFiles = directory.list { it.filter { it.toString().endsWith("_node.conf") }.toList() } val confFiles = directory.listEndingWith("_node.conf")
val webServerConfFiles = directory.list { it.filter { it.toString().endsWith("_web-server.conf") }.toList() } val webServerConfFiles = directory.listEndingWith("_web-server.conf")
for (confFile in confFiles) { for (confFile in confFiles) {
val nodeName = confFile.fileName.toString().removeSuffix("_node.conf") val nodeName = confFile.fileName.toString().removeSuffix("_node.conf")
@ -285,11 +295,10 @@ internal constructor(private val initSerEnv: Boolean,
cordaJar.copyToDirectory(nodeDir, REPLACE_EXISTING) cordaJar.copyToDirectory(nodeDir, REPLACE_EXISTING)
} }
directory.list { paths -> val nodeDirs = directory.list { subDir -> subDir.filter { (it / "node.conf").exists() && !(it / "corda.jar").exists() }.toList() }
paths.filter { (it / "node.conf").exists() && !(it / "corda.jar").exists() }.forEach { for (nodeDir in nodeDirs) {
println("Copying corda.jar into node directory ${it.fileName}") println("Copying corda.jar into node directory ${nodeDir.fileName}")
cordaJar.copyToDirectory(it) cordaJar.copyToDirectory(nodeDir)
}
} }
if (fromCordform) { if (fromCordform) {
@ -304,15 +313,11 @@ internal constructor(private val initSerEnv: Boolean,
} }
private fun gatherNodeDirectories(directory: Path): List<Path> { private fun gatherNodeDirectories(directory: Path): List<Path> {
return directory.list { paths -> val nodeDirs = directory.list { subDir -> subDir.filter { (it / "corda.jar").exists() }.toList() }
paths.filter { for (nodeDir in nodeDirs) {
val exists = (it / "corda.jar").exists() require((nodeDir / "node.conf").exists()) { "Missing node.conf in node directory ${nodeDir.fileName}" }
if (exists) {
require((it / "node.conf").exists()) { "Missing node.conf in node directory ${it.fileName}" }
}
exists
}.toList()
} }
return nodeDirs
} }
private fun distributeNodeInfos(nodeDirs: List<Path>, nodeInfoFiles: List<Path>) { private fun distributeNodeInfos(nodeDirs: List<Path>, nodeInfoFiles: List<Path>) {
@ -434,13 +439,13 @@ internal constructor(private val initSerEnv: Boolean,
private fun initialiseSerialization() { private fun initialiseSerialization() {
_contextSerializationEnv.set(SerializationEnvironment.with( _contextSerializationEnv.set(SerializationEnvironment.with(
SerializationFactoryImpl().apply { SerializationFactoryImpl().apply {
registerScheme(AMQPParametersSerializationScheme) registerScheme(AMQPParametersSerializationScheme())
}, },
AMQP_P2P_CONTEXT) AMQP_P2P_CONTEXT)
) )
} }
private object AMQPParametersSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) { private class AMQPParametersSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException() override fun rpcClientSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException() override fun rpcServerSerializerFactory(context: SerializationContext) = throw UnsupportedOperationException()
@ -526,7 +531,7 @@ enum class CopyCordapps {
fun copy(cordappJars: List<Path>, nodeDirs: List<Path>, networkAlreadyExists: Boolean, fromCordform: Boolean) { fun copy(cordappJars: List<Path>, nodeDirs: List<Path>, networkAlreadyExists: Boolean, fromCordform: Boolean) {
if (!fromCordform) { if (!fromCordform) {
println("Found the following CorDapps: ${cordappJars.map { it.fileName }}") println("Found the following CorDapps: ${cordappJars.map(Path::getFileName)}")
} }
this.copyTo(cordappJars, nodeDirs, networkAlreadyExists, fromCordform) this.copyTo(cordappJars, nodeDirs, networkAlreadyExists, fromCordform)
} }

View File

@ -23,10 +23,12 @@ import net.corda.testing.internal.createNodeInfoAndSigned
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After import org.junit.After
import org.junit.AfterClass
import org.junit.Rule import org.junit.Rule
import org.junit.Test import org.junit.Test
import org.junit.rules.ExpectedException import org.junit.rules.ExpectedException
import org.junit.rules.TemporaryFolder import org.junit.rules.TemporaryFolder
import java.nio.file.Files
import java.nio.file.Path import java.nio.file.Path
import java.security.PublicKey import java.security.PublicKey
import java.time.Duration import java.time.Duration
@ -45,13 +47,28 @@ class NetworkBootstrapperTest {
@JvmField @JvmField
val testSerialization = SerializationEnvironmentRule() val testSerialization = SerializationEnvironmentRule()
private val fakeEmbeddedCordaJar = fakeFileBytes() companion object {
private val fakeEmbeddedCorda = fakeFileBytes()
private val fakeEmbeddedCordaJar = Files.createTempFile("corda", ".jar").write(fakeEmbeddedCorda)
private val contractsJars = HashMap<Path, TestContractsJar>() private fun fakeFileBytes(writeToFile: Path? = null): ByteArray {
val bytes = secureRandomBytes(128)
writeToFile?.write(bytes)
return bytes
}
@JvmStatic
@AfterClass
fun cleanUp() {
Files.delete(fakeEmbeddedCordaJar)
}
}
private val contractsJars = hashMapOf<Path, TestContractsJar>()
private val bootstrapper = NetworkBootstrapper( private val bootstrapper = NetworkBootstrapper(
initSerEnv = false, initSerEnv = false,
embeddedCordaJar = fakeEmbeddedCordaJar::inputStream, embeddedCordaJar = { fakeEmbeddedCordaJar.toUri().toURL() },
nodeInfosGenerator = { nodeDirs -> nodeInfosGenerator = { nodeDirs ->
nodeDirs.map { nodeDir -> nodeDirs.map { nodeDir ->
val name = nodeDir.fakeNodeConfig.myLegalName val name = nodeDir.fakeNodeConfig.myLegalName
@ -101,7 +118,7 @@ class NetworkBootstrapperTest {
fun `single node conf file`() { fun `single node conf file`() {
createNodeConfFile("node1", bobConfig) createNodeConfFile("node1", bobConfig)
bootstrap() bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "node1" to bobConfig) val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "node1" to bobConfig)
networkParameters.run { networkParameters.run {
assertThat(epoch).isEqualTo(1) assertThat(epoch).isEqualTo(1)
assertThat(notaries).isEmpty() assertThat(notaries).isEmpty()
@ -121,7 +138,7 @@ class NetworkBootstrapperTest {
fun `single node directory with just node conf file`() { fun `single node directory with just node conf file`() {
createNodeDir("bob", bobConfig) createNodeDir("bob", bobConfig)
bootstrap() bootstrap()
assertBootstrappedNetwork(fakeEmbeddedCordaJar, "bob" to bobConfig) assertBootstrappedNetwork(fakeEmbeddedCorda, "bob" to bobConfig)
} }
@Test @Test
@ -147,7 +164,7 @@ class NetworkBootstrapperTest {
createNodeConfFile("alice", aliceConfig) createNodeConfFile("alice", aliceConfig)
createNodeConfFile("notary", notaryConfig) createNodeConfFile("notary", notaryConfig)
bootstrap() bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "notary" to notaryConfig) val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig, "notary" to notaryConfig)
networkParameters.assertContainsNotary("notary") networkParameters.assertContainsNotary("notary")
} }
@ -165,7 +182,7 @@ class NetworkBootstrapperTest {
createNodeConfFile("alice", aliceConfig) createNodeConfFile("alice", aliceConfig)
createNodeDir("bob", bobConfig) createNodeDir("bob", bobConfig)
bootstrap() bootstrap()
assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "bob" to bobConfig) assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig, "bob" to bobConfig)
} }
@Test @Test
@ -173,7 +190,7 @@ class NetworkBootstrapperTest {
createNodeConfFile("alice", aliceConfig) createNodeConfFile("alice", aliceConfig)
val cordappBytes = createFakeCordappJar("sample-app", listOf("contract.class")) val cordappBytes = createFakeCordappJar("sample-app", listOf("contract.class"))
bootstrap() bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig) val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig)
assertThat(rootDir / "alice" / "cordapps" / "sample-app.jar").hasBinaryContent(cordappBytes) assertThat(rootDir / "alice" / "cordapps" / "sample-app.jar").hasBinaryContent(cordappBytes)
assertThat(networkParameters.whitelistedContractImplementations).isEqualTo(mapOf( assertThat(networkParameters.whitelistedContractImplementations).isEqualTo(mapOf(
"contract.class" to listOf(cordappBytes.sha256()) "contract.class" to listOf(cordappBytes.sha256())
@ -185,7 +202,7 @@ class NetworkBootstrapperTest {
createNodeConfFile("alice", aliceConfig) createNodeConfFile("alice", aliceConfig)
val cordappBytes = createFakeCordappJar("sample-app", listOf("contract.class")) val cordappBytes = createFakeCordappJar("sample-app", listOf("contract.class"))
bootstrap(copyCordapps = CopyCordapps.No) bootstrap(copyCordapps = CopyCordapps.No)
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig) val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig)
assertThat(rootDir / "alice" / "cordapps" / "sample-app.jar").doesNotExist() assertThat(rootDir / "alice" / "cordapps" / "sample-app.jar").doesNotExist()
assertThat(networkParameters.whitelistedContractImplementations).isEqualTo(mapOf( assertThat(networkParameters.whitelistedContractImplementations).isEqualTo(mapOf(
"contract.class" to listOf(cordappBytes.sha256()) "contract.class" to listOf(cordappBytes.sha256())
@ -199,7 +216,7 @@ class NetworkBootstrapperTest {
val networkParameters1 = (rootDir / "alice").networkParameters val networkParameters1 = (rootDir / "alice").networkParameters
createNodeConfFile("bob", bobConfig) createNodeConfFile("bob", bobConfig)
bootstrap() bootstrap()
val networkParameters2 = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "bob" to bobConfig) val networkParameters2 = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig, "bob" to bobConfig)
assertThat(networkParameters1).isEqualTo(networkParameters2) assertThat(networkParameters1).isEqualTo(networkParameters2)
} }
@ -209,7 +226,7 @@ class NetworkBootstrapperTest {
bootstrap() bootstrap()
createNodeConfFile("notary", notaryConfig) createNodeConfFile("notary", notaryConfig)
bootstrap() bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "notary" to notaryConfig) val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig, "notary" to notaryConfig)
networkParameters.assertContainsNotary("notary") networkParameters.assertContainsNotary("notary")
assertThat(networkParameters.epoch).isEqualTo(2) assertThat(networkParameters.epoch).isEqualTo(2)
} }
@ -225,7 +242,7 @@ class NetworkBootstrapperTest {
maxMessageSize = maxMessageSize, maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize, maxTransactionSize = maxTransactionSize,
eventHorizon = eventHorizon) eventHorizon = eventHorizon)
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig) val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig)
assertThat(networkParameters.minimumPlatformVersion).isEqualTo(minimumPlatformVersion) assertThat(networkParameters.minimumPlatformVersion).isEqualTo(minimumPlatformVersion)
assertThat(networkParameters.maxMessageSize).isEqualTo(maxMessageSize) assertThat(networkParameters.maxMessageSize).isEqualTo(maxMessageSize)
assertThat(networkParameters.maxTransactionSize).isEqualTo(maxTransactionSize) assertThat(networkParameters.maxTransactionSize).isEqualTo(maxTransactionSize)
@ -299,12 +316,6 @@ class NetworkBootstrapperTest {
private val rootDir get() = tempFolder.root.toPath() private val rootDir get() = tempFolder.root.toPath()
private fun fakeFileBytes(writeToFile: Path? = null): ByteArray {
val bytes = secureRandomBytes(128)
writeToFile?.write(bytes)
return bytes
}
private fun bootstrap(copyCordapps: CopyCordapps = CopyCordapps.FirstRunOnly, private fun bootstrap(copyCordapps: CopyCordapps = CopyCordapps.FirstRunOnly,
packageOwnership: Map<String, PublicKey>? = emptyMap(), packageOwnership: Map<String, PublicKey>? = emptyMap(),
minimumPlatformVerison: Int? = PLATFORM_VERSION, minimumPlatformVerison: Int? = PLATFORM_VERSION,
@ -317,7 +328,7 @@ class NetworkBootstrapperTest {
maxMessageSize = maxMessageSize, maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize, maxTransactionSize = maxTransactionSize,
eventHorizon = eventHorizon, eventHorizon = eventHorizon,
packageOwnership = packageOwnership?.map { PackageOwner(it.key, it.value!!) } packageOwnership = packageOwnership?.map { PackageOwner(it.key, it.value) }
)) ))
} }
@ -364,7 +375,7 @@ class NetworkBootstrapperTest {
private fun assertBootstrappedNetwork(cordaJar: ByteArray, vararg nodes: Pair<String, FakeNodeConfig>): NetworkParameters { private fun assertBootstrappedNetwork(cordaJar: ByteArray, vararg nodes: Pair<String, FakeNodeConfig>): NetworkParameters {
val networkParameters = (rootDir / nodes[0].first).networkParameters val networkParameters = (rootDir / nodes[0].first).networkParameters
val allNodeInfoFiles = nodes.map { (rootDir / it.first).nodeInfoFile }.associateBy({ it }, { it.readAll() }) val allNodeInfoFiles = nodes.map { (rootDir / it.first).nodeInfoFile }.associateBy({ it }, Path::readAll)
for ((nodeDirName, config) in nodes) { for ((nodeDirName, config) in nodes) {
val nodeDir = rootDir / nodeDirName val nodeDir = rootDir / nodeDirName