mirror of
https://github.com/corda/corda.git
synced 2025-06-16 22:28:15 +00:00
Merge remote-tracking branch 'corda/master' into christians_os_merge_20171031
This commit is contained in:
@ -290,7 +290,7 @@ class FlowStackSnapshotTest {
|
||||
@Test
|
||||
fun `flowStackSnapshot object is serializable`() {
|
||||
val mockNet = MockNetwork(threadPerNode = true)
|
||||
mockNet.createNotaryNode(legalName = DUMMY_NOTARY.name)
|
||||
mockNet.createNotaryNode()
|
||||
val node = mockNet.createPartyNode()
|
||||
node.internals.registerInitiatedFlow(DummyFlow::class.java)
|
||||
node.services.startFlow(FlowStackSnapshotSerializationTestingFlow()).resultFuture.get()
|
||||
|
@ -28,13 +28,13 @@ fun ledger(
|
||||
initialiseSerialization: Boolean = true,
|
||||
dsl: LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter>.() -> Unit
|
||||
): LedgerDSL<TestTransactionDSLInterpreter, TestLedgerDSLInterpreter> {
|
||||
if (initialiseSerialization) initialiseTestSerialization()
|
||||
val serializationEnv = initialiseTestSerialization(initialiseSerialization)
|
||||
try {
|
||||
val ledgerDsl = LedgerDSL(TestLedgerDSLInterpreter(services))
|
||||
dsl(ledgerDsl)
|
||||
return ledgerDsl
|
||||
} finally {
|
||||
if (initialiseSerialization) resetTestSerialization()
|
||||
serializationEnv.resetTestSerialization()
|
||||
}
|
||||
}
|
||||
|
||||
@ -59,7 +59,6 @@ fun testNodeConfiguration(
|
||||
myLegalName: CordaX500Name): NodeConfiguration {
|
||||
abstract class MockableNodeConfiguration : NodeConfiguration // Otherwise Mockito is defeated by val getters.
|
||||
return rigorousMock<MockableNodeConfiguration>().also {
|
||||
doReturn(true).whenever(it).noNetworkMapServiceMode
|
||||
doReturn(baseDirectory).whenever(it).baseDirectory
|
||||
doReturn(myLegalName).whenever(it).myLegalName
|
||||
doReturn(1).whenever(it).minimumPlatformVersion
|
||||
@ -77,7 +76,7 @@ fun testNodeConfiguration(
|
||||
doReturn(VerifierType.InMemory).whenever(it).verifierType
|
||||
doReturn(5).whenever(it).messageRedeliveryDelaySeconds
|
||||
doReturn(0L).whenever(it).additionalNodeInfoPollingFrequencyMsec
|
||||
doReturn(null).whenever(it).networkMapService
|
||||
doReturn(null).whenever(it).devModeOptions
|
||||
doCallRealMethod().whenever(it).certificatesDirectory
|
||||
doCallRealMethod().whenever(it).trustStoreFile
|
||||
doCallRealMethod().whenever(it).sslKeystore
|
||||
|
@ -31,7 +31,6 @@ import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import net.corda.nodeapi.NodeInfoFilesCopier
|
||||
import net.corda.nodeapi.User
|
||||
import net.corda.nodeapi.config.parseAs
|
||||
import net.corda.nodeapi.config.toConfig
|
||||
import net.corda.nodeapi.internal.addShutdownHook
|
||||
import net.corda.testing.*
|
||||
@ -199,13 +198,13 @@ sealed class NodeHandle {
|
||||
* will be added and that will be used.
|
||||
*/
|
||||
abstract val rpc: CordaRPCOps
|
||||
abstract val configuration: FullNodeConfiguration
|
||||
abstract val configuration: NodeConfiguration
|
||||
abstract val webAddress: NetworkHostAndPort
|
||||
|
||||
data class OutOfProcess(
|
||||
override val nodeInfo: NodeInfo,
|
||||
override val rpc: CordaRPCOps,
|
||||
override val configuration: FullNodeConfiguration,
|
||||
override val configuration: NodeConfiguration,
|
||||
override val webAddress: NetworkHostAndPort,
|
||||
val debugPort: Int?,
|
||||
val process: Process,
|
||||
@ -224,7 +223,7 @@ sealed class NodeHandle {
|
||||
data class InProcess(
|
||||
override val nodeInfo: NodeInfo,
|
||||
override val rpc: CordaRPCOps,
|
||||
override val configuration: FullNodeConfiguration,
|
||||
override val configuration: NodeConfiguration,
|
||||
override val webAddress: NetworkHostAndPort,
|
||||
val node: StartedNode<Node>,
|
||||
val nodeThread: Thread,
|
||||
@ -406,7 +405,7 @@ fun <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericD
|
||||
coerce: (D) -> DI,
|
||||
dsl: DI.() -> A
|
||||
): A {
|
||||
if (initialiseSerialization) initialiseTestSerialization()
|
||||
val serializationEnv = initialiseTestSerialization(initialiseSerialization)
|
||||
val shutdownHook = addShutdownHook(driverDsl::shutdown)
|
||||
try {
|
||||
driverDsl.start()
|
||||
@ -417,7 +416,57 @@ fun <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericD
|
||||
} finally {
|
||||
driverDsl.shutdown()
|
||||
shutdownHook.cancel()
|
||||
if (initialiseSerialization) resetTestSerialization()
|
||||
serializationEnv.resetTestSerialization()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is a helper method to allow extending of the DSL, along the lines of
|
||||
* interface SomeOtherExposedDSLInterface : DriverDSLExposedInterface
|
||||
* interface SomeOtherInternalDSLInterface : DriverDSLInternalInterface, SomeOtherExposedDSLInterface
|
||||
* class SomeOtherDSL(val driverDSL : DriverDSL) : DriverDSLInternalInterface by driverDSL, SomeOtherInternalDSLInterface
|
||||
*
|
||||
* @param coerce We need this explicit coercion witness because we can't put an extra DI : D bound in a `where` clause.
|
||||
*/
|
||||
fun <DI : DriverDSLExposedInterface, D : DriverDSLInternalInterface, A> genericDriver(
|
||||
defaultParameters: DriverParameters = DriverParameters(),
|
||||
isDebug: Boolean = defaultParameters.isDebug,
|
||||
driverDirectory: Path = defaultParameters.driverDirectory,
|
||||
portAllocation: PortAllocation = defaultParameters.portAllocation,
|
||||
debugPortAllocation: PortAllocation = defaultParameters.debugPortAllocation,
|
||||
systemProperties: Map<String, String> = defaultParameters.systemProperties,
|
||||
useTestClock: Boolean = defaultParameters.useTestClock,
|
||||
initialiseSerialization: Boolean = defaultParameters.initialiseSerialization,
|
||||
startNodesInProcess: Boolean = defaultParameters.startNodesInProcess,
|
||||
extraCordappPackagesToScan: List<String> = defaultParameters.extraCordappPackagesToScan,
|
||||
driverDslWrapper: (DriverDSL) -> D,
|
||||
coerce: (D) -> DI,
|
||||
dsl: DI.() -> A
|
||||
): A {
|
||||
val serializationEnv = initialiseTestSerialization(initialiseSerialization)
|
||||
val driverDsl = driverDslWrapper(
|
||||
DriverDSL(
|
||||
portAllocation = portAllocation,
|
||||
debugPortAllocation = debugPortAllocation,
|
||||
systemProperties = systemProperties,
|
||||
driverDirectory = driverDirectory.toAbsolutePath(),
|
||||
useTestClock = useTestClock,
|
||||
isDebug = isDebug,
|
||||
startNodesInProcess = startNodesInProcess,
|
||||
extraCordappPackagesToScan = extraCordappPackagesToScan
|
||||
)
|
||||
)
|
||||
val shutdownHook = addShutdownHook(driverDsl::shutdown)
|
||||
try {
|
||||
driverDsl.start()
|
||||
return dsl(coerce(driverDsl))
|
||||
} catch (exception: Throwable) {
|
||||
log.error("Driver shutting down because of exception", exception)
|
||||
throw exception
|
||||
} finally {
|
||||
driverDsl.shutdown()
|
||||
shutdownHook.cancel()
|
||||
serializationEnv.resetTestSerialization()
|
||||
}
|
||||
}
|
||||
|
||||
@ -652,7 +701,7 @@ class DriverDSL(
|
||||
_executorService?.shutdownNow()
|
||||
}
|
||||
|
||||
private fun establishRpc(config: FullNodeConfiguration, processDeathFuture: CordaFuture<out Process>): CordaFuture<CordaRPCOps> {
|
||||
private fun establishRpc(config: NodeConfiguration, processDeathFuture: CordaFuture<out Process>): CordaFuture<CordaRPCOps> {
|
||||
val rpcAddress = config.rpcAddress!!
|
||||
val client = CordaRPCClient(rpcAddress)
|
||||
val connectionFuture = poll(executorService, "RPC connection") {
|
||||
@ -868,16 +917,8 @@ class DriverDSL(
|
||||
return future
|
||||
}
|
||||
|
||||
private fun startNodeInternal(name: CordaX500Name,config: Config, webAddress: NetworkHostAndPort, startInProcess: Boolean?, maximumHeapSize: String, logLevel: String? = null): CordaFuture<NodeHandle> {
|
||||
val globalDataSourceProperties = mutableMapOf<String, Any?>()
|
||||
val overriddenDatasourceUrl = systemProperties["dataSourceProperties.dataSource.url"]
|
||||
|
||||
overriddenDatasourceUrl?.let {
|
||||
val connectionString = overriddenDatasourceUrl + "/" + databaseNamesByNode.computeIfAbsent(name, { UUID.randomUUID().toString() })
|
||||
globalDataSourceProperties["dataSourceProperties.dataSource.url"] = connectionString
|
||||
}
|
||||
val enhancedConfig = config+ globalDataSourceProperties
|
||||
val nodeConfiguration = (enhancedConfig).parseAs<FullNodeConfiguration>()
|
||||
private fun startNodeInternal(config: Config, webAddress: NetworkHostAndPort, startInProcess: Boolean?, maximumHeapSize: String): CordaFuture<NodeHandle> {
|
||||
val nodeConfiguration = config.parseAsNodeConfiguration()
|
||||
nodeInfoFilesCopier.addConfig(nodeConfiguration.baseDirectory)
|
||||
val onNodeExit: () -> Unit = {
|
||||
nodeInfoFilesCopier.removeConfig(nodeConfiguration.baseDirectory)
|
||||
@ -947,7 +988,7 @@ class DriverDSL(
|
||||
|
||||
private fun startInProcessNode(
|
||||
executorService: ScheduledExecutorService,
|
||||
nodeConf: FullNodeConfiguration,
|
||||
nodeConf: NodeConfiguration,
|
||||
config: Config,
|
||||
cordappPackages: List<String>
|
||||
): CordaFuture<Pair<StartedNode<Node>, Thread>> {
|
||||
@ -966,7 +1007,7 @@ class DriverDSL(
|
||||
|
||||
private fun startOutOfProcessNode(
|
||||
executorService: ScheduledExecutorService,
|
||||
nodeConf: FullNodeConfiguration,
|
||||
nodeConf: NodeConfiguration,
|
||||
config: Config,
|
||||
quasarJarPath: String,
|
||||
debugPort: Int?,
|
||||
@ -989,7 +1030,7 @@ class DriverDSL(
|
||||
)
|
||||
// See experimental/quasar-hook/README.md for how to generate.
|
||||
val excludePattern = "x(antlr**;bftsmart**;ch**;co.paralleluniverse**;com.codahale**;com.esotericsoftware**;com.fasterxml**;com.google**;com.ibm**;com.intellij**;com.jcabi**;com.nhaarman**;com.opengamma**;com.typesafe**;com.zaxxer**;de.javakaffee**;groovy**;groovyjarjarantlr**;groovyjarjarasm**;io.atomix**;io.github**;io.netty**;jdk**;joptsimple**;junit**;kotlin**;net.bytebuddy**;net.i2p**;org.apache**;org.assertj**;org.bouncycastle**;org.codehaus**;org.crsh**;org.dom4j**;org.fusesource**;org.h2**;org.hamcrest**;org.hibernate**;org.jboss**;org.jcp**;org.joda**;org.junit**;org.mockito**;org.objectweb**;org.objenesis**;org.slf4j**;org.w3c**;org.xml**;org.yaml**;reflectasm**;rx**)"
|
||||
val extraJvmArguments = systemProperties.map { "-D${it.key}=${it.value}" } +
|
||||
val extraJvmArguments = systemProperties.removeResolvedClasspath().map { "-D${it.key}=${it.value}" } +
|
||||
"-javaagent:$quasarJarPath=$excludePattern"
|
||||
val loggingLevel = logLevel ?: if (debugPort == null) "INFO" else "DEBUG"
|
||||
|
||||
@ -1042,6 +1083,14 @@ class DriverDSL(
|
||||
.let { Class.forName(it.className).`package`?.name }
|
||||
?: throw IllegalStateException("Function instantiating driver must be defined in a package.")
|
||||
}
|
||||
|
||||
/**
|
||||
* We have an alternative way of specifying classpath for spawned process: by using "-cp" option. So duplicating the setting of this
|
||||
* rather long string is un-necessary and can be harmful on Windows.
|
||||
*/
|
||||
private fun Map<String, Any>.removeResolvedClasspath(): Map<String, Any> {
|
||||
return filterNot { it.key == "java.class.path" }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,9 +49,12 @@ object ProcessUtilities {
|
||||
addAll(arguments)
|
||||
}
|
||||
return ProcessBuilder(command).apply {
|
||||
if (errorLogPath != null) redirectError(errorLogPath.toFile()) // FIXME: Undone by inheritIO.
|
||||
inheritIO()
|
||||
if (workingDirectory != null) directory(workingDirectory.toFile())
|
||||
if (workingDirectory != null) {
|
||||
redirectError((workingDirectory / "$className.stderr.log").toFile())
|
||||
redirectOutput((workingDirectory / "$className.stdout.log").toFile())
|
||||
directory(workingDirectory.toFile())
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,115 @@
|
||||
package net.corda.testing.internal
|
||||
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.concurrent.fork
|
||||
import net.corda.core.internal.concurrent.transpose
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.node.internal.cordapp.CordappLoader
|
||||
import net.corda.node.services.config.ConfigHelper
|
||||
import net.corda.node.services.config.configOf
|
||||
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.driver.addressMustNotBeBoundFuture
|
||||
import net.corda.testing.getFreeLocalPorts
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.apache.logging.log4j.Level
|
||||
import org.junit.After
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.nio.file.Path
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
// TODO Some of the logic here duplicates what's in the driver
|
||||
abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyList()) {
|
||||
companion object {
|
||||
private val WHITESPACE = "\\s++".toRegex()
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule()
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
private val nodes = mutableListOf<StartedNode<Node>>()
|
||||
private val nodeInfos = mutableListOf<NodeInfo>()
|
||||
|
||||
init {
|
||||
System.setProperty("consoleLogLevel", Level.DEBUG.name().toLowerCase())
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the network map node and all the nodes started by [startNode]. This is called automatically after each test
|
||||
* but can also be called manually within a test.
|
||||
*/
|
||||
@After
|
||||
fun stopAllNodes() {
|
||||
val shutdownExecutor = Executors.newScheduledThreadPool(nodes.size)
|
||||
nodes.map { shutdownExecutor.fork(it::dispose) }.transpose().getOrThrow()
|
||||
// Wait until ports are released
|
||||
val portNotBoundChecks = nodes.flatMap {
|
||||
listOf(
|
||||
it.internals.configuration.p2pAddress.let { addressMustNotBeBoundFuture(shutdownExecutor, it) },
|
||||
it.internals.configuration.rpcAddress?.let { addressMustNotBeBoundFuture(shutdownExecutor, it) }
|
||||
)
|
||||
}.filterNotNull()
|
||||
nodes.clear()
|
||||
portNotBoundChecks.transpose().getOrThrow()
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun startNode(legalName: CordaX500Name,
|
||||
platformVersion: Int = 1,
|
||||
rpcUsers: List<User> = emptyList(),
|
||||
configOverrides: Map<String, Any> = emptyMap()): StartedNode<Node> {
|
||||
val baseDirectory = baseDirectory(legalName).createDirectories()
|
||||
val localPort = getFreeLocalPorts("localhost", 2)
|
||||
val p2pAddress = configOverrides["p2pAddress"] ?: localPort[0].toString()
|
||||
val config = ConfigHelper.loadConfig(
|
||||
baseDirectory = baseDirectory,
|
||||
allowMissingConfig = true,
|
||||
configOverrides = configOf(
|
||||
"myLegalName" to legalName.toString(),
|
||||
"p2pAddress" to p2pAddress,
|
||||
"rpcAddress" to localPort[1].toString(),
|
||||
"rpcUsers" to rpcUsers.map { it.toMap() }
|
||||
) + configOverrides
|
||||
)
|
||||
|
||||
val parsedConfig = config.parseAsNodeConfiguration()
|
||||
val node = Node(
|
||||
parsedConfig,
|
||||
MockServices.MOCK_VERSION_INFO.copy(platformVersion = platformVersion),
|
||||
initialiseSerialization = false,
|
||||
cordappLoader = CordappLoader.createDefaultWithTestPackages(parsedConfig, cordappPackages)).start()
|
||||
nodes += node
|
||||
ensureAllNetworkMapCachesHaveAllNodeInfos()
|
||||
thread(name = legalName.organisation) {
|
||||
node.internals.run()
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
protected fun baseDirectory(legalName: CordaX500Name): Path {
|
||||
return tempFolder.root.toPath() / legalName.organisation.replace(WHITESPACE, "")
|
||||
}
|
||||
|
||||
private fun ensureAllNetworkMapCachesHaveAllNodeInfos() {
|
||||
val runningNodes = nodes.filter { it.internals.started != null }
|
||||
val runningNodesInfo = runningNodes.map { it.info }
|
||||
for (node in runningNodes)
|
||||
for (nodeInfo in runningNodesInfo) {
|
||||
node.services.networkMapCache.addNode(nodeInfo)
|
||||
}
|
||||
}
|
||||
}
|
@ -1,9 +1,9 @@
|
||||
package net.corda.testing.node
|
||||
|
||||
import co.paralleluniverse.common.util.VisibleForTesting
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.Party
|
||||
import net.corda.core.internal.concurrent.doneFuture
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.services.NetworkMapCache
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
@ -27,32 +27,12 @@ class MockNetworkMapCache(database: CordaPersistence, configuration: NodeConfigu
|
||||
}
|
||||
|
||||
override val changed: Observable<NetworkMapCache.MapChange> = PublishSubject.create<NetworkMapCache.MapChange>()
|
||||
override val nodeReady: CordaFuture<Void?> get() = doneFuture(null)
|
||||
|
||||
init {
|
||||
val mockNodeA = NodeInfo(listOf(BANK_C_ADDR), listOf(BANK_C), 1, serial = 1L)
|
||||
val mockNodeB = NodeInfo(listOf(BANK_D_ADDR), listOf(BANK_D), 1, serial = 1L)
|
||||
partyNodes.add(mockNodeA)
|
||||
partyNodes.add(mockNodeB)
|
||||
runWithoutMapService()
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly add a registration to the internal cache. DOES NOT fire the change listeners, as it's
|
||||
* not a change being received.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun addRegistration(node: NodeInfo) {
|
||||
val previousIndex = partyNodes.indexOfFirst { it.legalIdentitiesAndCerts == node.legalIdentitiesAndCerts }
|
||||
if (previousIndex != -1) partyNodes[previousIndex] = node
|
||||
else partyNodes.add(node)
|
||||
}
|
||||
|
||||
/**
|
||||
* Directly remove a registration from the internal cache. DOES NOT fire the change listeners, as it's
|
||||
* not a change being received.
|
||||
*/
|
||||
@VisibleForTesting
|
||||
fun deleteRegistration(legalIdentity: Party): Boolean {
|
||||
return partyNodes.removeIf { legalIdentity.owningKey in it.legalIdentitiesAndCerts.map { it.owningKey } }
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ import net.corda.core.crypto.entropyToKeyPair
|
||||
import net.corda.core.crypto.random63BitValue
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.internal.concurrent.doneFuture
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.createDirectory
|
||||
import net.corda.core.internal.uncheckedCast
|
||||
@ -17,12 +16,10 @@ import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.messaging.SingleMessageRecipient
|
||||
import net.corda.core.node.services.IdentityService
|
||||
import net.corda.core.node.services.KeyManagementService
|
||||
import net.corda.core.node.services.PartyInfo
|
||||
import net.corda.core.serialization.SerializationWhitelist
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import net.corda.finance.utils.WorldMapLocation
|
||||
import net.corda.node.internal.AbstractNode
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.node.internal.cordapp.CordappLoader
|
||||
@ -32,7 +29,7 @@ import net.corda.node.services.config.BFTSMaRtConfiguration
|
||||
import net.corda.node.services.config.NodeConfiguration
|
||||
import net.corda.node.services.config.NotaryConfig
|
||||
import net.corda.node.services.keys.E2ETestKeyManagementService
|
||||
import net.corda.node.services.messaging.*
|
||||
import net.corda.node.services.messaging.MessagingService
|
||||
import net.corda.node.services.network.InMemoryNetworkMapService
|
||||
import net.corda.node.services.network.NetworkMapService
|
||||
import net.corda.node.services.transactions.BFTNonValidatingNotaryService
|
||||
@ -42,10 +39,8 @@ import net.corda.node.utilities.AffinityExecutor
|
||||
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
|
||||
import net.corda.nodeapi.internal.ServiceInfo
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.initialiseTestSerialization
|
||||
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
||||
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
|
||||
import net.corda.testing.resetTestSerialization
|
||||
import net.corda.testing.testNodeConfiguration
|
||||
import org.apache.activemq.artemis.utils.ReusableLatch
|
||||
import org.slf4j.Logger
|
||||
@ -54,9 +49,9 @@ import java.math.BigInteger
|
||||
import java.nio.file.Path
|
||||
import java.security.KeyPair
|
||||
import java.security.PublicKey
|
||||
import java.util.*
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import net.corda.testing.testNodeConfiguration
|
||||
|
||||
fun StartedNode<MockNetwork.MockNode>.pumpReceive(block: Boolean = false): InMemoryMessagingNetwork.MessageTransfer? {
|
||||
return (network as InMemoryMessagingNetwork.InMemoryMessaging).pumpReceive(block)
|
||||
@ -125,11 +120,10 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
||||
private val threadPerNode: Boolean = defaultParameters.threadPerNode,
|
||||
servicePeerAllocationStrategy: InMemoryMessagingNetwork.ServicePeerAllocationStrategy = defaultParameters.servicePeerAllocationStrategy,
|
||||
private val defaultFactory: Factory<*> = defaultParameters.defaultFactory,
|
||||
private val initialiseSerialization: Boolean = defaultParameters.initialiseSerialization,
|
||||
initialiseSerialization: Boolean = defaultParameters.initialiseSerialization,
|
||||
private val cordappPackages: List<String> = defaultParameters.cordappPackages) : Closeable {
|
||||
/** Helper constructor for creating a [MockNetwork] with custom parameters from Java. */
|
||||
constructor(parameters: MockNetworkParameters) : this(defaultParameters = parameters)
|
||||
|
||||
var nextNodeId = 0
|
||||
private set
|
||||
private val filesystem = Jimfs.newFileSystem(unix())
|
||||
@ -140,9 +134,9 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
||||
private val _nodes = mutableListOf<MockNode>()
|
||||
/** A read only view of the current set of executing nodes. */
|
||||
val nodes: List<MockNode> get() = _nodes
|
||||
private val serializationEnv = initialiseTestSerialization(initialiseSerialization)
|
||||
|
||||
init {
|
||||
if (initialiseSerialization) initialiseTestSerialization()
|
||||
filesystem.getPath("/nodes").createDirectory()
|
||||
}
|
||||
|
||||
@ -185,7 +179,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
||||
CordappLoader.createDefaultWithTestPackages(args.config, args.network.cordappPackages),
|
||||
args.network.busyLatch) {
|
||||
val mockNet = args.network
|
||||
override val networkMapAddress = null
|
||||
val id = args.id
|
||||
internal val notaryIdentity = args.notaryIdentity
|
||||
val entropyRoot = args.entropyRoot
|
||||
@ -238,11 +231,11 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
||||
return entropyToKeyPair(counter)
|
||||
}
|
||||
|
||||
// It's OK to not have a network map service in the mock network.
|
||||
override fun noNetworkMapConfigured() = doneFuture(Unit)
|
||||
|
||||
// There is no need to slow down the unit tests by initialising CityDatabase
|
||||
open fun findMyLocation(): WorldMapLocation? = null // It's left only for NetworkVisualiserSimulation
|
||||
/**
|
||||
* MockNetwork will ensure nodes are connected to each other. The nodes themselves
|
||||
* won't be able to tell if that happened already or not.
|
||||
*/
|
||||
override fun checkNetworkMapIsInitialized() = Unit
|
||||
|
||||
override fun makeTransactionVerifierService() = InMemoryTransactionVerifierService(1)
|
||||
|
||||
@ -253,13 +246,6 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
||||
val testSerializationWhitelists by lazy { super.serializationWhitelists.toMutableList() }
|
||||
override val serializationWhitelists: List<SerializationWhitelist>
|
||||
get() = testSerializationWhitelists
|
||||
|
||||
// This does not indirect through the NodeInfo object so it can be called before the node is started.
|
||||
// It is used from the network visualiser tool.
|
||||
@Suppress("unused")
|
||||
val place: WorldMapLocation
|
||||
get() = findMyLocation()!!
|
||||
|
||||
private var dbCloser: (() -> Any?)? = null
|
||||
override fun <T> initialiseDatabasePersistence(schemaService: SchemaService, insideTransaction: () -> T) = super.initialiseDatabasePersistence(schemaService) {
|
||||
dbCloser = database::close
|
||||
@ -354,10 +340,8 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun createNotaryNode(legalName: CordaX500Name = DUMMY_NOTARY.name, validating: Boolean = true): StartedNode<MockNode> {
|
||||
return createNode(MockNodeParameters(legalName = legalName, configOverrides = {
|
||||
doReturn(NotaryConfig(validating)).whenever(it).notary
|
||||
}))
|
||||
fun createNotaryNode(parameters: MockNodeParameters = MockNodeParameters(legalName = DUMMY_NOTARY.name), validating: Boolean = true): StartedNode<MockNode> {
|
||||
return createNotaryNode(parameters, validating, defaultFactory)
|
||||
}
|
||||
|
||||
fun <N : MockNode> createNotaryNode(parameters: MockNodeParameters = MockNodeParameters(legalName = DUMMY_NOTARY.name),
|
||||
@ -406,7 +390,7 @@ class MockNetwork(defaultParameters: MockNetworkParameters = MockNetworkParamete
|
||||
|
||||
fun stopNodes() {
|
||||
nodes.forEach { it.started?.dispose() }
|
||||
if (initialiseSerialization) resetTestSerialization()
|
||||
serializationEnv.resetTestSerialization()
|
||||
}
|
||||
|
||||
// Test method to block until all scheduled activity, active flows
|
||||
|
@ -9,10 +9,7 @@ import net.corda.core.identity.PartyAndCertificate
|
||||
import net.corda.core.messaging.DataFeed
|
||||
import net.corda.core.messaging.FlowHandle
|
||||
import net.corda.core.messaging.FlowProgressHandle
|
||||
import net.corda.core.node.AppServiceHub
|
||||
import net.corda.core.node.NodeInfo
|
||||
import net.corda.core.node.ServiceHub
|
||||
import net.corda.core.node.StateLoader
|
||||
import net.corda.core.node.*
|
||||
import net.corda.core.node.services.*
|
||||
import net.corda.core.serialization.SerializeAsToken
|
||||
import net.corda.core.serialization.SingletonSerializeAsToken
|
||||
@ -109,7 +106,7 @@ open class MockServices(
|
||||
|
||||
/**
|
||||
* Makes database and mock services appropriate for unit tests.
|
||||
* @param keys a list of [KeyPair] instances to be used by [MockServices]. Defualts to [MEGA_CORP_KEY]
|
||||
* @param keys a list of [KeyPair] instances to be used by [MockServices]. Defaults to [MEGA_CORP_KEY]
|
||||
* @param createIdentityService a lambda function returning an instance of [IdentityService]. Defauts to [InMemoryIdentityService].
|
||||
*
|
||||
* @return a pair where the first element is the instance of [CordaPersistence] and the second is [MockServices].
|
||||
@ -126,14 +123,14 @@ open class MockServices(
|
||||
val mockService = database.transaction {
|
||||
object : MockServices(cordappLoader, *(keys.toTypedArray())) {
|
||||
override val identityService: IdentityService = database.transaction { identityServiceRef }
|
||||
override val vaultService = makeVaultService(database.hibernateConfig)
|
||||
override val vaultService: VaultServiceInternal = makeVaultService(database.hibernateConfig)
|
||||
|
||||
override fun recordTransactions(notifyVault: Boolean, txs: Iterable<SignedTransaction>) {
|
||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||
for (stx in txs) {
|
||||
validatedTransactions.addTransaction(stx)
|
||||
}
|
||||
// Refactored to use notifyAll() as we have no other unit test for that method with multiple transactions.
|
||||
vaultService.notifyAll(txs.map { it.tx })
|
||||
vaultService.notifyAll(statesToRecord, txs.map { it.tx })
|
||||
}
|
||||
|
||||
override fun jdbcSession(): Connection = database.createSession()
|
||||
@ -150,7 +147,7 @@ open class MockServices(
|
||||
|
||||
val key: KeyPair get() = keys.first()
|
||||
|
||||
override fun recordTransactions(notifyVault: Boolean, txs: Iterable<SignedTransaction>) {
|
||||
override fun recordTransactions(statesToRecord: StatesToRecord, txs: Iterable<SignedTransaction>) {
|
||||
txs.forEach {
|
||||
stateMachineRecordedTransactionMapping.addMapping(StateMachineRunId.createRandom(), it.id)
|
||||
}
|
||||
|
@ -1,205 +0,0 @@
|
||||
package net.corda.testing.node
|
||||
|
||||
import net.corda.core.concurrent.CordaFuture
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.concurrent.*
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.div
|
||||
import net.corda.core.utilities.NetworkHostAndPort
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.node.internal.Node
|
||||
import net.corda.node.internal.StartedNode
|
||||
import net.corda.node.internal.cordapp.CordappLoader
|
||||
import net.corda.node.services.config.*
|
||||
import net.corda.node.utilities.ServiceIdentityGenerator
|
||||
import net.corda.nodeapi.User
|
||||
import net.corda.nodeapi.config.parseAs
|
||||
import net.corda.nodeapi.config.toConfig
|
||||
import net.corda.testing.DUMMY_MAP
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.driver.addressMustNotBeBoundFuture
|
||||
import net.corda.testing.getFreeLocalPorts
|
||||
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
|
||||
import org.apache.logging.log4j.Level
|
||||
import org.junit.After
|
||||
import org.junit.Rule
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/**
|
||||
* Extend this class if you need to run nodes in a test. You could use the driver DSL but it's extremely slow for testing
|
||||
* purposes. Use the driver if you need to run the nodes in separate processes otherwise this class will suffice.
|
||||
*/
|
||||
// TODO Some of the logic here duplicates what's in the driver
|
||||
abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyList()) : TestDependencyInjectionBase() {
|
||||
companion object {
|
||||
private val WHITESPACE = "\\s++".toRegex()
|
||||
}
|
||||
|
||||
@Rule
|
||||
@JvmField
|
||||
val tempFolder = TemporaryFolder()
|
||||
|
||||
private val nodes = mutableListOf<StartedNode<Node>>()
|
||||
private var _networkMapNode: StartedNode<Node>? = null
|
||||
|
||||
val networkMapNode: StartedNode<Node> get() = _networkMapNode ?: startNetworkMapNode()
|
||||
|
||||
init {
|
||||
System.setProperty("consoleLogLevel", Level.DEBUG.name().toLowerCase())
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops the network map node and all the nodes started by [startNode]. This is called automatically after each test
|
||||
* but can also be called manually within a test.
|
||||
*/
|
||||
@After
|
||||
fun stopAllNodes() {
|
||||
val shutdownExecutor = Executors.newScheduledThreadPool(nodes.size)
|
||||
nodes.map { shutdownExecutor.fork(it::dispose) }.transpose().getOrThrow()
|
||||
// Wait until ports are released
|
||||
val portNotBoundChecks = nodes.flatMap {
|
||||
listOf(
|
||||
it.internals.configuration.p2pAddress.let { addressMustNotBeBoundFuture(shutdownExecutor, it) },
|
||||
it.internals.configuration.rpcAddress?.let { addressMustNotBeBoundFuture(shutdownExecutor, it) }
|
||||
)
|
||||
}.filterNotNull()
|
||||
nodes.clear()
|
||||
_networkMapNode = null
|
||||
portNotBoundChecks.transpose().getOrThrow()
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear network map data from nodes' databases.
|
||||
*/
|
||||
fun clearAllNodeInfoDb() {
|
||||
nodes.forEach { it.services.networkMapCache.clearNetworkMapCache() }
|
||||
}
|
||||
|
||||
/**
|
||||
* You can use this method to start the network map node in a more customised manner. Otherwise it
|
||||
* will automatically be started with the default parameters.
|
||||
*/
|
||||
fun startNetworkMapNode(legalName: CordaX500Name = DUMMY_MAP.name,
|
||||
platformVersion: Int = 1,
|
||||
rpcUsers: List<User> = emptyList(),
|
||||
configOverrides: Map<String, Any> = emptyMap()): StartedNode<Node> {
|
||||
check(_networkMapNode == null || _networkMapNode!!.info.legalIdentitiesAndCerts.first().name == legalName)
|
||||
return startNodeInternal(legalName, platformVersion, rpcUsers, configOverrides).apply {
|
||||
_networkMapNode = this
|
||||
}
|
||||
}
|
||||
|
||||
@JvmOverloads
|
||||
fun startNode(legalName: CordaX500Name,
|
||||
platformVersion: Int = 1,
|
||||
rpcUsers: List<User> = emptyList(),
|
||||
configOverrides: Map<String, Any> = emptyMap(),
|
||||
noNetworkMap: Boolean = false,
|
||||
waitForConnection: Boolean = true): CordaFuture<StartedNode<Node>> {
|
||||
val networkMapConf = if (noNetworkMap) {
|
||||
// Nonexistent network map service address.
|
||||
mapOf(
|
||||
"networkMapService" to mapOf(
|
||||
"address" to "localhost:10000",
|
||||
"legalName" to networkMapNode.info.legalIdentitiesAndCerts.first().name.toString()
|
||||
)
|
||||
)
|
||||
} else {
|
||||
mapOf(
|
||||
"networkMapService" to mapOf(
|
||||
"address" to networkMapNode.internals.configuration.p2pAddress.toString(),
|
||||
"legalName" to networkMapNode.info.legalIdentitiesAndCerts.first().name.toString()
|
||||
)
|
||||
)
|
||||
}
|
||||
val node = startNodeInternal(
|
||||
legalName,
|
||||
platformVersion,
|
||||
rpcUsers,
|
||||
networkMapConf + configOverrides,
|
||||
noNetworkMap)
|
||||
return if (waitForConnection) node.internals.nodeReadyFuture.map { node } else doneFuture(node)
|
||||
}
|
||||
|
||||
// TODO This method has been added temporarily, to be deleted once the set of notaries is defined at the network level.
|
||||
fun startNotaryNode(name: CordaX500Name,
|
||||
rpcUsers: List<User> = emptyList(),
|
||||
validating: Boolean = true): CordaFuture<StartedNode<Node>> {
|
||||
return startNode(name, rpcUsers = rpcUsers, configOverrides = mapOf("notary" to mapOf("validating" to validating)))
|
||||
}
|
||||
|
||||
fun startNotaryCluster(notaryName: CordaX500Name, clusterSize: Int): CordaFuture<List<StartedNode<Node>>> {
|
||||
fun notaryConfig(nodeAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): Map<String, Any> {
|
||||
val clusterAddresses = if (clusterAddress != null) listOf(clusterAddress) else emptyList()
|
||||
val config = NotaryConfig(validating = true, raft = RaftConfig(nodeAddress = nodeAddress, clusterAddresses = clusterAddresses))
|
||||
return mapOf("notary" to config.toConfig().root().unwrapped())
|
||||
}
|
||||
|
||||
ServiceIdentityGenerator.generateToDisk(
|
||||
(0 until clusterSize).map { baseDirectory(notaryName.copy(organisation = "${notaryName.organisation}-$it")) },
|
||||
notaryName)
|
||||
|
||||
val nodeAddresses = getFreeLocalPorts("localhost", clusterSize)
|
||||
|
||||
val masterNodeFuture = startNode(
|
||||
CordaX500Name(organisation = "${notaryName.organisation}-0", locality = notaryName.locality, country = notaryName.country),
|
||||
configOverrides = notaryConfig(nodeAddresses[0]) + mapOf(
|
||||
"database" to mapOf(
|
||||
"serverNameTablePrefix" to if (clusterSize > 1) "${notaryName.organisation}0".replace(Regex("[^0-9A-Za-z]+"), "") else ""
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
val remainingNodesFutures = (1 until clusterSize).map {
|
||||
startNode(
|
||||
CordaX500Name(organisation = "${notaryName.organisation}-$it", locality = notaryName.locality, country = notaryName.country),
|
||||
configOverrides = notaryConfig(nodeAddresses[it], nodeAddresses[0]) + mapOf(
|
||||
"database" to mapOf(
|
||||
"serverNameTablePrefix" to "${notaryName.organisation}$it".replace(Regex("[^0-9A-Za-z]+"), "")
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
return remainingNodesFutures.transpose().flatMap { remainingNodes ->
|
||||
masterNodeFuture.map { masterNode -> listOf(masterNode) + remainingNodes }
|
||||
}
|
||||
}
|
||||
|
||||
protected fun baseDirectory(legalName: CordaX500Name) = tempFolder.root.toPath() / legalName.organisation.replace(WHITESPACE, "")
|
||||
|
||||
private fun startNodeInternal(legalName: CordaX500Name,
|
||||
platformVersion: Int,
|
||||
rpcUsers: List<User>,
|
||||
configOverrides: Map<String, Any>,
|
||||
noNetworkMap: Boolean = false): StartedNode<Node> {
|
||||
val baseDirectory = baseDirectory(legalName).createDirectories()
|
||||
val localPort = getFreeLocalPorts("localhost", 2)
|
||||
val p2pAddress = configOverrides["p2pAddress"] ?: localPort[0].toString()
|
||||
val config = ConfigHelper.loadConfig(
|
||||
baseDirectory = baseDirectory,
|
||||
allowMissingConfig = true,
|
||||
configOverrides = configOf(
|
||||
"myLegalName" to legalName.toString(),
|
||||
"p2pAddress" to p2pAddress,
|
||||
"rpcAddress" to localPort[1].toString(),
|
||||
"rpcUsers" to rpcUsers.map { it.toMap() },
|
||||
"noNetworkMap" to noNetworkMap
|
||||
) + configOverrides
|
||||
)
|
||||
|
||||
val parsedConfig = config.parseAs<FullNodeConfiguration>()
|
||||
val node = Node(
|
||||
parsedConfig,
|
||||
MOCK_VERSION_INFO.copy(platformVersion = platformVersion),
|
||||
initialiseSerialization = false,
|
||||
cordappLoader = CordappLoader.createDefaultWithTestPackages(parsedConfig, cordappPackages)).start()
|
||||
nodes += node
|
||||
thread(name = legalName.organisation) {
|
||||
node.internals.run()
|
||||
}
|
||||
return node
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user