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.amqpMagic
import java.io.File
import java.io.InputStream
import java.net.URL
import java.nio.file.FileAlreadyExistsException
import java.nio.file.Path
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
@ -53,14 +53,14 @@ import kotlin.streams.toList
class NetworkBootstrapper
@VisibleForTesting
internal constructor(private val initSerEnv: Boolean,
private val embeddedCordaJar: () -> InputStream,
private val embeddedCordaJar: () -> URL,
private val nodeInfosGenerator: (List<Path>) -> List<Path>,
private val contractsJarConverter: (Path) -> ContractsJar) : NetworkBootstrapperWithOverridableParameters {
constructor() : this(
initSerEnv = true,
embeddedCordaJar = Companion::extractEmbeddedCordaJar,
nodeInfosGenerator = Companion::generateNodeInfos,
embeddedCordaJar = ::extractEmbeddedCordaJar,
nodeInfosGenerator = ::generateNodeInfos,
contractsJarConverter = ::ContractsJarFile
)
@ -77,8 +77,8 @@ internal constructor(private val initSerEnv: Boolean,
private val jarsThatArentCordapps = setOf("corda.jar", "runnodes.jar")
private fun extractEmbeddedCordaJar(): InputStream {
return Thread.currentThread().contextClassLoader.getResourceAsStream("corda.jar")
private fun extractEmbeddedCordaJar(): URL {
return Thread.currentThread().contextClassLoader.getResource("corda.jar")
}
private fun generateNodeInfos(nodeDirs: List<Path>): List<Path> {
@ -106,13 +106,19 @@ internal constructor(private val initSerEnv: Boolean,
.redirectOutput(nodeInfoGenFile)
.apply { environment()["CAPSULE_CACHE_DIR"] = "../.cache" }
.start()
if (!process.waitFor(3, TimeUnit.MINUTES)) {
try {
if (!process.waitFor(3, TimeUnit.MINUTES)) {
process.destroyForcibly()
printNodeInfoGenLogToConsole(nodeInfoGenFile)
}
printNodeInfoGenLogToConsole(nodeInfoGenFile) { process.exitValue() == 0 }
return nodeDir.list { paths ->
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()
printNodeInfoGenLogToConsole(nodeInfoGenFile)
}
printNodeInfoGenLogToConsole(nodeInfoGenFile) { process.exitValue() == 0 }
return nodeDir.list { paths ->
paths.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }.findFirst().get()
throw e
}
}
@ -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 {
var networkAlreadyExists = false
val cordaJar = directory / "corda.jar"
var usingEmbedded = false
if (!cordaJar.exists()) {
embeddedCordaJar().use { it.copyTo(cordaJar) }
embeddedCordaJar().openStream().use { it.copyTo(cordaJar) }
usingEmbedded = true
} else if (!fromCordform) {
println("Using corda.jar in root directory")
}
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() }
val confFiles = directory.listEndingWith("_node.conf")
val webServerConfFiles = directory.listEndingWith("_web-server.conf")
for (confFile in confFiles) {
val nodeName = confFile.fileName.toString().removeSuffix("_node.conf")
@ -285,11 +295,10 @@ internal constructor(private val initSerEnv: Boolean,
cordaJar.copyToDirectory(nodeDir, REPLACE_EXISTING)
}
directory.list { paths ->
paths.filter { (it / "node.conf").exists() && !(it / "corda.jar").exists() }.forEach {
println("Copying corda.jar into node directory ${it.fileName}")
cordaJar.copyToDirectory(it)
}
val nodeDirs = directory.list { subDir -> subDir.filter { (it / "node.conf").exists() && !(it / "corda.jar").exists() }.toList() }
for (nodeDir in nodeDirs) {
println("Copying corda.jar into node directory ${nodeDir.fileName}")
cordaJar.copyToDirectory(nodeDir)
}
if (fromCordform) {
@ -304,15 +313,11 @@ internal constructor(private val initSerEnv: Boolean,
}
private fun gatherNodeDirectories(directory: Path): List<Path> {
return directory.list { paths ->
paths.filter {
val exists = (it / "corda.jar").exists()
if (exists) {
require((it / "node.conf").exists()) { "Missing node.conf in node directory ${it.fileName}" }
}
exists
}.toList()
val nodeDirs = directory.list { subDir -> subDir.filter { (it / "corda.jar").exists() }.toList() }
for (nodeDir in nodeDirs) {
require((nodeDir / "node.conf").exists()) { "Missing node.conf in node directory ${nodeDir.fileName}" }
}
return nodeDirs
}
private fun distributeNodeInfos(nodeDirs: List<Path>, nodeInfoFiles: List<Path>) {
@ -434,13 +439,13 @@ internal constructor(private val initSerEnv: Boolean,
private fun initialiseSerialization() {
_contextSerializationEnv.set(SerializationEnvironment.with(
SerializationFactoryImpl().apply {
registerScheme(AMQPParametersSerializationScheme)
registerScheme(AMQPParametersSerializationScheme())
},
AMQP_P2P_CONTEXT)
)
}
private object AMQPParametersSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
private class AMQPParametersSerializationScheme : AbstractAMQPSerializationScheme(emptyList()) {
override fun rpcClientSerializerFactory(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) {
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)
}

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.assertThatThrownBy
import org.junit.After
import org.junit.AfterClass
import org.junit.Rule
import org.junit.Test
import org.junit.rules.ExpectedException
import org.junit.rules.TemporaryFolder
import java.nio.file.Files
import java.nio.file.Path
import java.security.PublicKey
import java.time.Duration
@ -45,13 +47,28 @@ class NetworkBootstrapperTest {
@JvmField
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(
initSerEnv = false,
embeddedCordaJar = fakeEmbeddedCordaJar::inputStream,
embeddedCordaJar = { fakeEmbeddedCordaJar.toUri().toURL() },
nodeInfosGenerator = { nodeDirs ->
nodeDirs.map { nodeDir ->
val name = nodeDir.fakeNodeConfig.myLegalName
@ -101,7 +118,7 @@ class NetworkBootstrapperTest {
fun `single node conf file`() {
createNodeConfFile("node1", bobConfig)
bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "node1" to bobConfig)
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "node1" to bobConfig)
networkParameters.run {
assertThat(epoch).isEqualTo(1)
assertThat(notaries).isEmpty()
@ -121,7 +138,7 @@ class NetworkBootstrapperTest {
fun `single node directory with just node conf file`() {
createNodeDir("bob", bobConfig)
bootstrap()
assertBootstrappedNetwork(fakeEmbeddedCordaJar, "bob" to bobConfig)
assertBootstrappedNetwork(fakeEmbeddedCorda, "bob" to bobConfig)
}
@Test
@ -147,7 +164,7 @@ class NetworkBootstrapperTest {
createNodeConfFile("alice", aliceConfig)
createNodeConfFile("notary", notaryConfig)
bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "notary" to notaryConfig)
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig, "notary" to notaryConfig)
networkParameters.assertContainsNotary("notary")
}
@ -165,7 +182,7 @@ class NetworkBootstrapperTest {
createNodeConfFile("alice", aliceConfig)
createNodeDir("bob", bobConfig)
bootstrap()
assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "bob" to bobConfig)
assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig, "bob" to bobConfig)
}
@Test
@ -173,7 +190,7 @@ class NetworkBootstrapperTest {
createNodeConfFile("alice", aliceConfig)
val cordappBytes = createFakeCordappJar("sample-app", listOf("contract.class"))
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(networkParameters.whitelistedContractImplementations).isEqualTo(mapOf(
"contract.class" to listOf(cordappBytes.sha256())
@ -185,7 +202,7 @@ class NetworkBootstrapperTest {
createNodeConfFile("alice", aliceConfig)
val cordappBytes = createFakeCordappJar("sample-app", listOf("contract.class"))
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(networkParameters.whitelistedContractImplementations).isEqualTo(mapOf(
"contract.class" to listOf(cordappBytes.sha256())
@ -199,7 +216,7 @@ class NetworkBootstrapperTest {
val networkParameters1 = (rootDir / "alice").networkParameters
createNodeConfFile("bob", bobConfig)
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)
}
@ -209,7 +226,7 @@ class NetworkBootstrapperTest {
bootstrap()
createNodeConfFile("notary", notaryConfig)
bootstrap()
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig, "notary" to notaryConfig)
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig, "notary" to notaryConfig)
networkParameters.assertContainsNotary("notary")
assertThat(networkParameters.epoch).isEqualTo(2)
}
@ -225,7 +242,7 @@ class NetworkBootstrapperTest {
maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize,
eventHorizon = eventHorizon)
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCordaJar, "alice" to aliceConfig)
val networkParameters = assertBootstrappedNetwork(fakeEmbeddedCorda, "alice" to aliceConfig)
assertThat(networkParameters.minimumPlatformVersion).isEqualTo(minimumPlatformVersion)
assertThat(networkParameters.maxMessageSize).isEqualTo(maxMessageSize)
assertThat(networkParameters.maxTransactionSize).isEqualTo(maxTransactionSize)
@ -299,12 +316,6 @@ class NetworkBootstrapperTest {
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,
packageOwnership: Map<String, PublicKey>? = emptyMap(),
minimumPlatformVerison: Int? = PLATFORM_VERSION,
@ -317,7 +328,7 @@ class NetworkBootstrapperTest {
maxMessageSize = maxMessageSize,
maxTransactionSize = maxTransactionSize,
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 {
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) {
val nodeDir = rootDir / nodeDirName