mirror of
https://github.com/corda/corda.git
synced 2024-12-20 21:43:14 +00:00
Another approach to fixing deployNodes task and network parameters generation (#2066)
* Generate networkParameteres for Cordformation. Fix deployNodes task in Cordformation to generate NetworkParameters before running the nodes. Add TestNetworkParametersGenerator utility loaded after node infos generation step. * Get rid of bouncy castle provider dependency For cordform-common. It caused problems with loading our custom X509EdDSAEngine for generation of network parameters in deployNodes task.
This commit is contained in:
parent
572c4af40c
commit
c9f3e98795
@ -1,4 +1,4 @@
|
||||
gradlePluginsVersion=2.0.9
|
||||
gradlePluginsVersion=3.0.0-NETWORKMAP
|
||||
kotlinVersion=1.1.60
|
||||
guavaVersion=21.0
|
||||
bouncycastleVersion=1.57
|
||||
|
@ -13,9 +13,6 @@ group 'net.corda.plugins'
|
||||
dependencies {
|
||||
// TypeSafe Config: for simple and human friendly config files.
|
||||
compile "com.typesafe:config:$typesafe_config_version"
|
||||
|
||||
// Bouncy Castle: for X.500 distinguished name manipulation
|
||||
compile "org.bouncycastle:bcprov-jdk15on:$bouncycastle_version"
|
||||
}
|
||||
|
||||
publish {
|
||||
|
@ -1,6 +1,5 @@
|
||||
package net.corda.cordform;
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface CordformContext {
|
||||
|
@ -0,0 +1,16 @@
|
||||
package net.corda.cordform;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public interface NetworkParametersGenerator {
|
||||
/**
|
||||
* Run generation of network parameters for [Cordformation]. Nodes need to have already their own [NodeInfo] files in their
|
||||
* base directories, these files will be used to extract notary identities.
|
||||
*
|
||||
* @param nodesDirs - nodes directories that will be used for network parameters generation. Network parameters
|
||||
* file will be dropped into each directory on this list.
|
||||
*/
|
||||
void run(List<Path> nodesDirs);
|
||||
}
|
@ -3,15 +3,14 @@ package net.corda.plugins
|
||||
import groovy.lang.Closure
|
||||
import net.corda.cordform.CordformDefinition
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.cordform.NetworkParametersGenerator
|
||||
import org.apache.tools.ant.filters.FixCrLfFilter
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.GradleException
|
||||
import org.gradle.api.plugins.JavaPluginConvention
|
||||
import org.gradle.api.tasks.SourceSet.MAIN_SOURCE_SET_NAME
|
||||
import org.gradle.api.tasks.TaskAction
|
||||
import java.io.File
|
||||
import java.net.URLClassLoader
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.util.concurrent.TimeUnit
|
||||
@ -28,6 +27,7 @@ open class Cordform : DefaultTask() {
|
||||
*/
|
||||
@Suppress("MemberVisibilityCanPrivate")
|
||||
var definitionClass: String? = null
|
||||
private val networkParametersGenClass: String = "net.corda.nodeapi.internal.TestNetworkParametersGenerator"
|
||||
private var directory = Paths.get("build", "nodes")
|
||||
private val nodes = mutableListOf<Node>()
|
||||
|
||||
@ -113,6 +113,19 @@ open class Cordform : DefaultTask() {
|
||||
.newInstance()
|
||||
}
|
||||
|
||||
/**
|
||||
* The parametersGenerator needn't be compiled until just before our build method, so we load it manually via sourceSets.main.runtimeClasspath.
|
||||
*/
|
||||
private fun loadParametersGenerator(): NetworkParametersGenerator {
|
||||
val plugin = project.convention.getPlugin(JavaPluginConvention::class.java)
|
||||
val classpath = plugin.sourceSets.getByName(MAIN_SOURCE_SET_NAME).runtimeClasspath
|
||||
val urls = classpath.files.map { it.toURI().toURL() }.toTypedArray()
|
||||
return URLClassLoader(urls, NetworkParametersGenerator::class.java.classLoader)
|
||||
.loadClass(networkParametersGenClass)
|
||||
.asSubclass(NetworkParametersGenerator::class.java)
|
||||
.newInstance()
|
||||
}
|
||||
|
||||
/**
|
||||
* This task action will create and install the nodes based on the node configurations added.
|
||||
*/
|
||||
@ -124,6 +137,7 @@ open class Cordform : DefaultTask() {
|
||||
installRunScript()
|
||||
nodes.forEach(Node::build)
|
||||
generateAndInstallNodeInfos()
|
||||
generateAndInstallNetworkParameters()
|
||||
}
|
||||
|
||||
private fun initializeConfiguration() {
|
||||
@ -142,6 +156,12 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun generateAndInstallNetworkParameters() {
|
||||
project.logger.info("Generating and installing network parameters")
|
||||
val networkParamsGenerator = loadParametersGenerator()
|
||||
networkParamsGenerator.run(nodes.map { it.fullPath() })
|
||||
}
|
||||
|
||||
private fun generateAndInstallNodeInfos() {
|
||||
generateNodeInfos()
|
||||
installNodeInfos()
|
||||
@ -149,7 +169,7 @@ open class Cordform : DefaultTask() {
|
||||
|
||||
private fun generateNodeInfos() {
|
||||
project.logger.info("Generating node infos")
|
||||
var nodeProcesses = buildNodeProcesses()
|
||||
val nodeProcesses = buildNodeProcesses()
|
||||
try {
|
||||
validateNodeProcessess(nodeProcesses)
|
||||
} finally {
|
||||
@ -158,9 +178,10 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
|
||||
private fun buildNodeProcesses(): Map<Node, Process> {
|
||||
return nodes
|
||||
.map { buildNodeProcess(it) }
|
||||
.toMap()
|
||||
val command = generateNodeInfoCommand()
|
||||
return nodes.map {
|
||||
it.makeLogDirectory()
|
||||
buildProcess(it, command, "generate-info.log") }.toMap()
|
||||
}
|
||||
|
||||
private fun validateNodeProcessess(nodeProcesses: Map<Node, Process>) {
|
||||
@ -175,14 +196,13 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildNodeProcess(node: Node): Pair<Node, Process> {
|
||||
node.makeLogDirectory()
|
||||
var process = ProcessBuilder(generateNodeInfoCommand())
|
||||
private fun buildProcess(node: Node, command: List<String>, logFile: String): Pair<Node, Process> {
|
||||
val process = ProcessBuilder(command)
|
||||
.directory(node.fullPath().toFile())
|
||||
.redirectErrorStream(true)
|
||||
// InheritIO causes hangs on windows due the gradle buffer also not being flushed.
|
||||
// Must redirect to output or logger (node log is still written, this is just startup banner)
|
||||
.redirectOutput(node.logFile().toFile())
|
||||
.redirectOutput(node.logFile(logFile).toFile())
|
||||
.addEnvironment("CAPSULE_CACHE_DIR", Node.capsuleCacheDir)
|
||||
.start()
|
||||
return Pair(node, process)
|
||||
@ -224,6 +244,6 @@ open class Cordform : DefaultTask() {
|
||||
}
|
||||
}
|
||||
}
|
||||
private fun Node.logFile(): Path = this.logDirectory().resolve("generate-info.log")
|
||||
private fun Node.logFile(name: String): Path = this.logDirectory().resolve(name)
|
||||
private fun ProcessBuilder.addEnvironment(key: String, value: String) = this.apply { environment().put(key, value) }
|
||||
}
|
||||
|
@ -2,9 +2,6 @@ package net.corda.plugins
|
||||
|
||||
import com.typesafe.config.*
|
||||
import net.corda.cordform.CordformNode
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x500.RDN
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle
|
||||
import org.gradle.api.Project
|
||||
import java.io.File
|
||||
import java.nio.charset.StandardCharsets
|
||||
@ -122,18 +119,10 @@ class Node(private val project: Project) : CordformNode() {
|
||||
project.logger.error("Node has a null name - cannot create node")
|
||||
throw IllegalStateException("Node has a null name - cannot create node")
|
||||
}
|
||||
|
||||
val dirName = try {
|
||||
val o = X500Name(name).getRDNs(BCStyle.O)
|
||||
if (o.size > 0) {
|
||||
o.first().first.value.toString()
|
||||
} else {
|
||||
name
|
||||
}
|
||||
} catch(_ : IllegalArgumentException) {
|
||||
// Can't parse as an X500 name, use the full string
|
||||
name
|
||||
}
|
||||
// Parsing O= part directly because importing BouncyCastle provider in Cordformation causes problems
|
||||
// with loading our custom X509EdDSAEngine.
|
||||
val organizationName = name.trim().split(",").firstOrNull { it.startsWith("O=") }?.substringAfter("=")
|
||||
val dirName = organizationName ?: name
|
||||
nodeDir = File(rootDir.toFile(), dirName)
|
||||
}
|
||||
|
||||
@ -151,7 +140,7 @@ class Node(private val project: Project) : CordformNode() {
|
||||
* Installs the corda fat JAR to the node directory.
|
||||
*/
|
||||
private fun installCordaJar() {
|
||||
val cordaJar = verifyAndGetCordaJar()
|
||||
val cordaJar = verifyAndGetRuntimeJar("corda")
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(cordaJar)
|
||||
@ -166,7 +155,7 @@ class Node(private val project: Project) : CordformNode() {
|
||||
* Installs the corda webserver JAR to the node directory
|
||||
*/
|
||||
private fun installWebserverJar() {
|
||||
val webJar = verifyAndGetWebserverJar()
|
||||
val webJar = verifyAndGetRuntimeJar("corda-webserver")
|
||||
project.copy {
|
||||
it.apply {
|
||||
from(webJar)
|
||||
@ -250,34 +239,17 @@ class Node(private val project: Project) : CordformNode() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the corda JAR amongst the dependencies.
|
||||
* Find the given JAR amongst the dependencies
|
||||
* @param jarName JAR name without the version part, for example for corda-2.0-SNAPSHOT.jar provide only "corda" as jarName
|
||||
*
|
||||
* @return A file representing the Corda JAR.
|
||||
* @return A file representing found JAR
|
||||
*/
|
||||
private fun verifyAndGetCordaJar(): File {
|
||||
val maybeCordaJAR = project.configuration("runtime").filter {
|
||||
it.toString().contains("corda-$releaseVersion.jar") || it.toString().contains("corda-enterprise-$releaseVersion.jar")
|
||||
}
|
||||
if (maybeCordaJAR.isEmpty) {
|
||||
throw RuntimeException("No Corda Capsule JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-$releaseVersion.jar\"")
|
||||
} else {
|
||||
val cordaJar = maybeCordaJAR.singleFile
|
||||
assert(cordaJar.isFile)
|
||||
return cordaJar
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the corda JAR amongst the dependencies
|
||||
*
|
||||
* @return A file representing the Corda webserver JAR
|
||||
*/
|
||||
private fun verifyAndGetWebserverJar(): File {
|
||||
private fun verifyAndGetRuntimeJar(jarName: String): File {
|
||||
val maybeJar = project.configuration("runtime").filter {
|
||||
it.toString().contains("corda-webserver-$releaseVersion.jar")
|
||||
"$jarName-$releaseVersion.jar" in it.toString() || "$jarName-enterprise-$releaseVersion.jar" in it.toString()
|
||||
}
|
||||
if (maybeJar.isEmpty) {
|
||||
throw RuntimeException("No Corda Webserver JAR found. Have you deployed the Corda project to Maven? Looked for \"corda-webserver-$releaseVersion.jar\"")
|
||||
throw IllegalStateException("No $jarName JAR found. Have you deployed the Corda project to Maven? Looked for \"$jarName-$releaseVersion.jar\"")
|
||||
} else {
|
||||
val jar = maybeJar.singleFile
|
||||
assert(jar.isFile)
|
||||
|
@ -1,4 +1,4 @@
|
||||
package net.corda.testing.common.internal
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
@ -0,0 +1,115 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.cordform.NetworkParametersGenerator
|
||||
import net.corda.core.crypto.SignedData
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.internal.list
|
||||
import net.corda.core.internal.readAll
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.internal.SerializationEnvironmentImpl
|
||||
import net.corda.core.serialization.internal._contextSerializationEnv
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.days
|
||||
import net.corda.nodeapi.internal.serialization.*
|
||||
import net.corda.nodeapi.internal.serialization.amqp.AMQPServerSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.kryo.AbstractKryoSerializationScheme
|
||||
import net.corda.nodeapi.internal.serialization.kryo.KryoHeaderV0_1
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
import kotlin.streams.toList
|
||||
|
||||
// This class is used by deployNodes task to generate NetworkParameters in [Cordformation].
|
||||
@Suppress("UNUSED")
|
||||
class TestNetworkParametersGenerator : NetworkParametersGenerator {
|
||||
companion object {
|
||||
private val logger = contextLogger()
|
||||
}
|
||||
|
||||
override fun run(nodesDirs: List<Path>) {
|
||||
logger.info("NetworkParameters generation using node directories: $nodesDirs")
|
||||
try {
|
||||
initialiseSerialization()
|
||||
val notaryInfos = loadAndGatherNotaryIdentities(nodesDirs)
|
||||
val copier = NetworkParametersCopier(NetworkParameters(
|
||||
minimumPlatformVersion = 1,
|
||||
notaries = notaryInfos,
|
||||
modifiedTime = Instant.now(),
|
||||
eventHorizon = 10000.days,
|
||||
maxMessageSize = 40000,
|
||||
maxTransactionSize = 40000,
|
||||
epoch = 1
|
||||
))
|
||||
nodesDirs.forEach { copier.install(it) }
|
||||
} finally {
|
||||
_contextSerializationEnv.set(null)
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadAndGatherNotaryIdentities(nodesDirs: List<Path>): List<NotaryInfo> {
|
||||
val infos = getAllNodeInfos(nodesDirs)
|
||||
val configs = nodesDirs.map { ConfigFactory.parseFile((it / "node.conf").toFile()) }
|
||||
val notaryConfigs = configs.filter { it.hasPath("notary") }
|
||||
val notaries = notaryConfigs.associateBy(
|
||||
{ CordaX500Name.parse(it.getString("myLegalName")) },
|
||||
{ it.getConfig("notary").getBoolean("validating") }
|
||||
)
|
||||
// Now get the notary identities based on names passed from configs. There is one problem, for distributed notaries
|
||||
// in config we specify only node's main name, the notary identity isn't passed there. It's read from keystore on
|
||||
// node startup, so we have to look it up from node info as a second identity, which is ugly.
|
||||
return infos.mapNotNull {
|
||||
info -> notaries[info.legalIdentities[0].name]?.let { NotaryInfo(info.notaryIdentity(), it) }
|
||||
}.distinct()
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads latest NodeInfo files stored in node's base directory.
|
||||
* Scans main directory and [CordformNode.NODE_INFO_DIRECTORY].
|
||||
* Signatures are checked before returning a value. The latest value stored for a given name is returned.
|
||||
*
|
||||
* @return list of latest [NodeInfo]s
|
||||
*/
|
||||
private fun getAllNodeInfos(nodesDirs: List<Path>): List<NodeInfo> {
|
||||
val nodeInfoFiles = nodesDirs.map { dir -> dir.list { it.filter { "nodeInfo-" in it.toString() }.toList()[0] } } // We take the first one only
|
||||
return nodeInfoFiles.mapNotNull { processFile(it) }
|
||||
}
|
||||
|
||||
private fun processFile(file: Path): NodeInfo? {
|
||||
return try {
|
||||
logger.info("Reading NodeInfo from file: $file")
|
||||
val signedData = file.readAll().deserialize<SignedData<NodeInfo>>()
|
||||
signedData.verified()
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Exception parsing NodeInfo from file. $file", e)
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
private fun NodeInfo.notaryIdentity() = if (legalIdentities.size == 2) legalIdentities[1] else legalIdentities[0]
|
||||
|
||||
// We need to to set serialization env, because generation of parameters is run from Cordform.
|
||||
// KryoServerSerializationScheme is not accessible from nodeapi.
|
||||
private fun initialiseSerialization() {
|
||||
val context = if (java.lang.Boolean.getBoolean("net.corda.testing.amqp.enable")) AMQP_P2P_CONTEXT else KRYO_P2P_CONTEXT
|
||||
_contextSerializationEnv.set(SerializationEnvironmentImpl(
|
||||
SerializationFactoryImpl().apply {
|
||||
registerScheme(KryoParametersSerializationScheme)
|
||||
registerScheme(AMQPServerSerializationScheme())
|
||||
},
|
||||
context))
|
||||
}
|
||||
|
||||
private object KryoParametersSerializationScheme : AbstractKryoSerializationScheme() {
|
||||
override fun canDeserializeVersion(byteSequence: ByteSequence, target: SerializationContext.UseCase): Boolean {
|
||||
return byteSequence == KryoHeaderV0_1 && target == SerializationContext.UseCase.P2P
|
||||
}
|
||||
|
||||
override fun rpcClientKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||
override fun rpcServerKryoPool(context: SerializationContext) = throw UnsupportedOperationException()
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ import net.corda.node.services.transactions.minCorrectReplicas
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import net.corda.nodeapi.internal.NotaryInfo
|
||||
import net.corda.testing.chooseIdentity
|
||||
import net.corda.testing.common.internal.NetworkParametersCopier
|
||||
import net.corda.nodeapi.internal.NetworkParametersCopier
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.contracts.DummyContract
|
||||
import net.corda.testing.dummyCommand
|
||||
|
@ -63,6 +63,7 @@ import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.slf4j.Logger
|
||||
import rx.Observable
|
||||
import java.io.IOException
|
||||
import java.io.NotSerializableException
|
||||
import java.lang.reflect.InvocationTargetException
|
||||
import java.security.KeyPair
|
||||
import java.security.KeyStoreException
|
||||
|
@ -27,6 +27,7 @@ class RPCMessagingClient(private val config: SSLConfiguration, serverAddress: Ne
|
||||
}
|
||||
|
||||
fun stop() = synchronized(this) {
|
||||
rpcServer?.close()
|
||||
artemis.stop()
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ dependencies {
|
||||
// Specify your cordapp's dependencies below, including dependent cordapps
|
||||
compile group: 'commons-io', name: 'commons-io', version: '2.5'
|
||||
|
||||
testCompile project(':node-driver')
|
||||
cordaCompile project(':node-driver')
|
||||
testCompile "junit:junit:$junit_version"
|
||||
testCompile "org.assertj:assertj-core:${assertj_version}"
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ fun <A> springDriver(
|
||||
useTestClock: Boolean = defaultParameters.useTestClock,
|
||||
initialiseSerialization: Boolean = defaultParameters.initialiseSerialization,
|
||||
startNodesInProcess: Boolean = defaultParameters.startNodesInProcess,
|
||||
notarySpecs: List<NotarySpec>,
|
||||
notarySpecs: List<NotarySpec> = defaultParameters.notarySpecs,
|
||||
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
|
||||
dsl: SpringDriverExposedDSLInterface.() -> A
|
||||
) = genericDriver(
|
||||
|
@ -36,7 +36,7 @@ import net.corda.nodeapi.config.toConfig
|
||||
import net.corda.nodeapi.internal.NotaryInfo
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.common.internal.NetworkParametersCopier
|
||||
import net.corda.nodeapi.internal.NetworkParametersCopier
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.internal.ProcessUtilities
|
||||
import net.corda.testing.node.ClusterSpec
|
||||
|
@ -16,7 +16,7 @@ import net.corda.node.services.config.parseAsNodeConfiguration
|
||||
import net.corda.node.services.config.plus
|
||||
import net.corda.nodeapi.User
|
||||
import net.corda.testing.SerializationEnvironmentRule
|
||||
import net.corda.testing.common.internal.NetworkParametersCopier
|
||||
import net.corda.nodeapi.internal.NetworkParametersCopier
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.driver.addressMustNotBeBoundFuture
|
||||
import net.corda.testing.getFreeLocalPorts
|
||||
|
@ -40,7 +40,7 @@ import net.corda.node.utilities.CordaPersistence
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import net.corda.nodeapi.internal.NotaryInfo
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.common.internal.NetworkParametersCopier
|
||||
import net.corda.nodeapi.internal.NetworkParametersCopier
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
|
@ -8,7 +8,7 @@ import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.contextLogger
|
||||
import net.corda.testing.common.internal.NetworkParametersCopier
|
||||
import net.corda.nodeapi.internal.NetworkParametersCopier
|
||||
import net.corda.testing.common.internal.testNetworkParameters
|
||||
import net.corda.testing.common.internal.asContextEnv
|
||||
import java.nio.file.Path
|
||||
|
Loading…
Reference in New Issue
Block a user