mirror of
https://github.com/corda/corda.git
synced 2025-06-14 05:08:18 +00:00
Merge Open Source to Enterprise (#79)
* Check array size before accessing * Review fixes * CORDA-540: Make Verifier work in AMQP mode (#1870) * reference to finance module via not hardcoded group ID (#1515) * generic way to reference to group id when loading finance.jar via cordapp * Fixed the node shell to work with the DataFeed class * Attempt to make NodeStatePersistenceTests more stable (#1895) By ensuring that the nodes are properly started and aware of each other before firing any flows through them. Also minor refactoring. * Disable unstable test on Windows (#1899) * CORDA-530 Don't soft-lock non-fungible states (#1794) * Don't run unlock query if nothing was locked * Constructors should not have side-effects * [CORDA-442] let Driver run without network map (#1890) * [CORDA-442] let Driver run without network map - Nodes started by driver run without a networkMapNode. - Driver does not take a networkMapStartStrategy anymore - a new parameter in the configuration "noNetworkMapServiceMode" allows for a node not to be a networkMapNode nor to connect to one. - Driver now waits for each node to write its own NodeInfo file to disk and then copies it into each other node. - When driver starts a node N, it waits for every node to be have N nodes in their network map. Note: the code to copy around the NodeInfo files was already in DemoBench, the NodeInfoFilesCopier class was just moved from DemoBench into core (I'm very open to core not being the best place, please advise) * Added missing cordappPackage dependencies. (#1894) * Eliminate circular dependency of NodeSchedulerService on ServiceHub. (#1891) * Update customSchemas documentation. (#1902) * [CORDA-694] Commands visibility for Oracles (without sacrificing privacy) (#1835) new checkCommandVisibility feature for Oracles * CORDA-599 PersistentNetworkMapCache no longer circularly depends on SH (#1652) * CORDA-725 - Change AMQP identifier to officially assigned value This does change our header format so pre-cached test files need regenerating * CORDA-725 - update changelog * CORDA-680 Update cordapp packages documentation (#1901) * Introduce MockNetworkParameters * Cordformation in Kotlin (#1873) Cordformation rewritten in kotlin. * Kotlin migration * Review Comments * CORDA-704: Implement `@DoNotImplement` annotation (#1903) * Enhance the API Scanner plugin to monitor class annotations. * Implement @DoNotImplement annotation, and apply it. * Update API definition. * Update API change detection to handle @DoNotImplement. * Document the `@DoNotImplement` annotation. * Experimental support for PostgreSQL (#1525) * Cash selection refactoring such that 3d party DB providers are only required to implement Coin Selection SQL logic. * Re-added debug logging statement. * Updated to include PR review feedback from VK * Refactoring following rebase from master. * Fix broken JUnits following rebase. * Use JDBC ResultSet getBlob() and added custom serializer to address concern raised by tomtau in PR. * Fix failing JUnits. * Experimental support for PostgreSQL: CashSelection done using window functions * Moved postgresql version information into corda/build.gradle * Using a PreparedStatement in CashSelectionPostgreSQLImpl * Changed the PostgreSQL Cash Selection implementation to use the new refactored AbstractCashSelection * * Retire MockServiceHubInternal (#1909) * Introduce rigorousMock * Add test-utils and node-driver to generated documentation * Fix-up: Bank Of Corda sample (#1912) In the previous version when running with `--role ISSUER` the application failed to start. The reason was that in spite of `quantity` and `currency` were optional, un-necessary `requestParams` been constructed regardless. * move SMM * Interface changes for multi-threading * CORDA-351: added dependency check plugin to gradle build script (#1911) * CORDA-351: added dependency check plugin to gradle build script * CORDA-351: Added suppression stub file with example * CORDA-351: added suppresionFile property * CORDA-435 - Ensure Kryo only tests use Kryo serializatin context Also correct lambda typos (from lamba) * Network map service REST API wrapper (#1907) * Network map client - WIP * Java doc and doc for doc site * remove javax.ws dependency * NetworkParameter -> NetworkParameters * move network map client to node * Fix jetty test dependencies * NetworkParameter -> NetworkParameters * Address PR issues * Address PR issues and unit test fix * Address PR issues * Fixing Bank-Of-Corda Demo in `master` (#1922) * Fix-up: Bank Of Corda sample Use correct CorDapp packages to scan (cherry picked from commit2caa134
) * Set adequate permissions for the nodes such that NodeExplorer can connect (cherry picked from commitae88242
) * Set adequate permissions for the nodes such that NodeExplorer can connect (cherry picked from commitae88242
) * Correct run configuration * Fix-up port numbers * CORDA-435 - AMQP serialisation cannot work with private vals They won't be reported as properties by the introspector and thus we will fail to find a constructor for them. This makes sense as we will be unable to serialise an object whose members we cannot read * CORDA-435 - AMQP enablement fixes AMQP has different serialization rules than Kryo surrounding the way we introspect objects to work out how to construct them * [CORDA-442] make MockNetwork not start a networkmap node (#1908) * [CORDA-442] make MockNetwork not start a networkmap node Now MockNetwork will put the appropriate NodeInfos inside each running node networkMapCache. Tests relating to networkmap node starting and interaction have been removed since they where relaying on MockNetwork * Minor fix for api checker script to support macOS * Retrofit changes from Enterprise PR #61 (#1934) * Introduce MockNodeParameters/Args (#1923) * CORDA-736 Add some new features to corda.jar via node.conf for testing (#1926) * CORDA-699 Add injection or modification of memory network messages (#1920) * Updated API stability changeset to reflect new schema attribute name.
This commit is contained in:
@ -18,6 +18,8 @@ dependencies {
|
||||
// TODO: Remove this dependency and the code that requires it
|
||||
compile "commons-fileupload:commons-fileupload:$fileupload_version"
|
||||
|
||||
compile "net.corda.plugins:cordform-common:$gradle_plugins_version"
|
||||
|
||||
// TypeSafe Config: for simple and human friendly config files.
|
||||
compile "com.typesafe:config:$typesafe_config_version"
|
||||
|
||||
|
@ -0,0 +1,165 @@
|
||||
package net.corda.nodeapi
|
||||
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.core.internal.ThreadBox
|
||||
import net.corda.core.internal.createDirectories
|
||||
import net.corda.core.internal.isRegularFile
|
||||
import net.corda.core.internal.list
|
||||
import net.corda.core.utilities.loggerFor
|
||||
import rx.Observable
|
||||
import rx.Scheduler
|
||||
import rx.Subscription
|
||||
import rx.schedulers.Schedulers
|
||||
import java.io.IOException
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.StandardCopyOption.COPY_ATTRIBUTES
|
||||
import java.nio.file.StandardCopyOption.REPLACE_EXISTING
|
||||
import java.nio.file.attribute.BasicFileAttributes
|
||||
import java.nio.file.attribute.FileTime
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Utility class which copies nodeInfo files across a set of running nodes.
|
||||
*
|
||||
* This class will create paths that it needs to poll and to where it needs to copy files in case those
|
||||
* don't exist yet.
|
||||
*/
|
||||
class NodeInfoFilesCopier(scheduler: Scheduler = Schedulers.io()) : AutoCloseable {
|
||||
|
||||
companion object {
|
||||
private val log = loggerFor<NodeInfoFilesCopier>()
|
||||
const val NODE_INFO_FILE_NAME_PREFIX = "nodeInfo-"
|
||||
}
|
||||
|
||||
private val nodeDataMapBox = ThreadBox(mutableMapOf<Path, NodeData>())
|
||||
/**
|
||||
* Whether the NodeInfoFilesCopier is closed. When the NodeInfoFilesCopier is closed it will stop polling the
|
||||
* filesystem and all the public methods except [#close] will throw.
|
||||
*/
|
||||
private var closed = false
|
||||
private val subscription: Subscription
|
||||
|
||||
init {
|
||||
this.subscription = Observable.interval(5, TimeUnit.SECONDS, scheduler)
|
||||
.subscribe { poll() }
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nodeDir a path to be watched for NodeInfos
|
||||
* Add a path of a node which is about to be started.
|
||||
* Its nodeInfo file will be copied to other nodes' additional-node-infos directory, and conversely,
|
||||
* other nodes' nodeInfo files will be copied to this node additional-node-infos directory.
|
||||
*/
|
||||
fun addConfig(nodeDir: Path) {
|
||||
require(!closed) { "NodeInfoFilesCopier is already closed" }
|
||||
nodeDataMapBox.locked {
|
||||
val newNodeFile = NodeData(nodeDir)
|
||||
put(nodeDir, newNodeFile)
|
||||
|
||||
for (previouslySeenFile in allPreviouslySeenFiles()) {
|
||||
atomicCopy(previouslySeenFile, newNodeFile.additionalNodeInfoDirectory.resolve(previouslySeenFile.fileName))
|
||||
}
|
||||
log.info("Now watching: $nodeDir")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param nodeConfig the configuration to be removed.
|
||||
* Remove the configuration of a node which is about to be stopped or already stopped.
|
||||
* No files written by that node will be copied to other nodes, nor files from other nodes will be copied to this
|
||||
* one.
|
||||
*/
|
||||
fun removeConfig(nodeDir: Path) {
|
||||
require(!closed) { "NodeInfoFilesCopier is already closed" }
|
||||
nodeDataMapBox.locked {
|
||||
remove(nodeDir) ?: return
|
||||
log.info("Stopped watching: $nodeDir")
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
require(!closed) { "NodeInfoFilesCopier is already closed" }
|
||||
nodeDataMapBox.locked {
|
||||
clear()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stops polling the filesystem.
|
||||
* This function can be called as many times as one wants.
|
||||
*/
|
||||
override fun close() {
|
||||
if (!closed) {
|
||||
closed = true
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
}
|
||||
|
||||
private fun allPreviouslySeenFiles() = nodeDataMapBox.alreadyLocked { values.flatMap { it.previouslySeenFiles.keys } }
|
||||
|
||||
private fun poll() {
|
||||
nodeDataMapBox.locked {
|
||||
for (nodeData in values) {
|
||||
nodeData.nodeDir.list { paths ->
|
||||
paths.filter { it.isRegularFile() }
|
||||
.filter { it.fileName.toString().startsWith(NODE_INFO_FILE_NAME_PREFIX) }
|
||||
.forEach { path -> processPath(nodeData, path) }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Takes a path under nodeData config dir and decides whether the file represented by that path needs to
|
||||
// be copied.
|
||||
private fun processPath(nodeData: NodeData, path: Path) {
|
||||
nodeDataMapBox.alreadyLocked {
|
||||
val newTimestamp = Files.readAttributes(path, BasicFileAttributes::class.java).lastModifiedTime()
|
||||
val previousTimestamp = nodeData.previouslySeenFiles.put(path, newTimestamp) ?: FileTime.fromMillis(-1)
|
||||
if (newTimestamp > previousTimestamp) {
|
||||
for (destination in this.values.filter { it.nodeDir != nodeData.nodeDir }.map { it.additionalNodeInfoDirectory }) {
|
||||
val fullDestinationPath = destination.resolve(path.fileName)
|
||||
atomicCopy(path, fullDestinationPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun atomicCopy(source: Path, destination: Path) {
|
||||
val tempDestination = try {
|
||||
Files.createTempFile(destination.parent, "", null)
|
||||
} catch (exception: IOException) {
|
||||
log.warn("Couldn't create a temporary file to copy $source", exception)
|
||||
throw exception
|
||||
}
|
||||
try {
|
||||
// First copy the file to a temporary file within the appropriate directory.
|
||||
Files.copy(source, tempDestination, COPY_ATTRIBUTES, REPLACE_EXISTING)
|
||||
} catch (exception: IOException) {
|
||||
log.warn("Couldn't copy $source to $tempDestination.", exception)
|
||||
Files.delete(tempDestination)
|
||||
throw exception
|
||||
}
|
||||
try {
|
||||
// Then rename it to the desired name. This way the file 'appears' on the filesystem as an atomic operation.
|
||||
Files.move(tempDestination, destination, REPLACE_EXISTING)
|
||||
} catch (exception: IOException) {
|
||||
log.warn("Couldn't move $tempDestination to $destination.", exception)
|
||||
Files.delete(tempDestination)
|
||||
throw exception
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience holder for all the paths and files relative to a single node.
|
||||
*/
|
||||
private class NodeData(val nodeDir: Path) {
|
||||
val additionalNodeInfoDirectory: Path = nodeDir.resolve(CordformNode.NODE_INFO_DIRECTORY)
|
||||
// Map from Path to its lastModifiedTime.
|
||||
val previouslySeenFiles = mutableMapOf<Path, FileTime>()
|
||||
|
||||
init {
|
||||
additionalNodeInfoDirectory.createDirectories()
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,8 @@
|
||||
package net.corda.nodeapi
|
||||
|
||||
import net.corda.core.serialization.deserialize
|
||||
import net.corda.core.serialization.serialize
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.transactions.LedgerTransaction
|
||||
import net.corda.core.utilities.sequence
|
||||
import org.apache.activemq.artemis.api.core.SimpleString
|
||||
import org.apache.activemq.artemis.api.core.client.ClientMessage
|
||||
import org.apache.activemq.artemis.reader.MessageUtil
|
||||
@ -20,12 +20,15 @@ object VerifierApi {
|
||||
val responseAddress: SimpleString
|
||||
) {
|
||||
companion object {
|
||||
fun fromClientMessage(message: ClientMessage): VerificationRequest {
|
||||
return VerificationRequest(
|
||||
fun fromClientMessage(message: ClientMessage): ObjectWithCompatibleContext<VerificationRequest> {
|
||||
val bytes = ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }
|
||||
val bytesSequence = bytes.sequence()
|
||||
val (transaction, context) = bytesSequence.deserializeWithCompatibleContext<LedgerTransaction>()
|
||||
val request = VerificationRequest(
|
||||
message.getLongProperty(VERIFICATION_ID_FIELD_NAME),
|
||||
ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }.deserialize(),
|
||||
MessageUtil.getJMSReplyTo(message)
|
||||
)
|
||||
transaction,
|
||||
MessageUtil.getJMSReplyTo(message))
|
||||
return ObjectWithCompatibleContext(request, context)
|
||||
}
|
||||
}
|
||||
|
||||
@ -49,11 +52,11 @@ object VerifierApi {
|
||||
}
|
||||
}
|
||||
|
||||
fun writeToClientMessage(message: ClientMessage) {
|
||||
fun writeToClientMessage(message: ClientMessage, context: SerializationContext) {
|
||||
message.putLongProperty(VERIFICATION_ID_FIELD_NAME, verificationId)
|
||||
if (exception != null) {
|
||||
message.putBytesProperty(RESULT_EXCEPTION_FIELD_NAME, exception.serialize().bytes)
|
||||
message.putBytesProperty(RESULT_EXCEPTION_FIELD_NAME, exception.serialize(context = context).bytes)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,6 @@ package net.corda.nodeapi.internal.serialization
|
||||
import net.corda.core.crypto.sha256
|
||||
import net.corda.core.internal.AbstractAttachment
|
||||
|
||||
class GeneratedAttachment(bytes: ByteArray) : AbstractAttachment({ bytes }) {
|
||||
class GeneratedAttachment(val bytes: ByteArray) : AbstractAttachment({ bytes }) {
|
||||
override val id = bytes.sha256()
|
||||
}
|
||||
|
@ -19,6 +19,7 @@ import net.corda.core.serialization.*
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.nodeapi.internal.AttachmentsClassLoader
|
||||
import org.slf4j.LoggerFactory
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.NotSerializableException
|
||||
import java.util.*
|
||||
@ -37,7 +38,7 @@ object NotSupportedSerializationScheme : SerializationScheme {
|
||||
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> = doThrow()
|
||||
}
|
||||
|
||||
data class SerializationContextImpl(override val preferredSerializationVersion: ByteSequence,
|
||||
data class SerializationContextImpl(override val preferredSerializationVersion: VersionHeader,
|
||||
override val deserializationClassLoader: ClassLoader,
|
||||
override val whitelist: ClassWhitelist,
|
||||
override val properties: Map<Any, Any>,
|
||||
@ -88,36 +89,54 @@ data class SerializationContextImpl(override val preferredSerializationVersion:
|
||||
})
|
||||
}
|
||||
|
||||
override fun withPreferredSerializationVersion(versionHeader: ByteSequence) = copy(preferredSerializationVersion = versionHeader)
|
||||
override fun withPreferredSerializationVersion(versionHeader: VersionHeader) = copy(preferredSerializationVersion = versionHeader)
|
||||
}
|
||||
|
||||
private const val HEADER_SIZE: Int = 8
|
||||
|
||||
fun ByteSequence.obtainHeaderSignature(): VersionHeader = take(HEADER_SIZE).copy()
|
||||
|
||||
open class SerializationFactoryImpl : SerializationFactory() {
|
||||
private val creator: List<StackTraceElement> = Exception().stackTrace.asList()
|
||||
|
||||
private val registeredSchemes: MutableCollection<SerializationScheme> = Collections.synchronizedCollection(mutableListOf())
|
||||
|
||||
private val logger = LoggerFactory.getLogger(javaClass)
|
||||
|
||||
// TODO: This is read-mostly. Probably a faster implementation to be found.
|
||||
private val schemes: ConcurrentHashMap<Pair<ByteSequence, SerializationContext.UseCase>, SerializationScheme> = ConcurrentHashMap()
|
||||
|
||||
private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): SerializationScheme {
|
||||
private fun schemeFor(byteSequence: ByteSequence, target: SerializationContext.UseCase): Pair<SerializationScheme, VersionHeader> {
|
||||
// truncate sequence to 8 bytes, and make sure it's a copy to avoid holding onto large ByteArrays
|
||||
return schemes.computeIfAbsent(byteSequence.take(HEADER_SIZE).copy() to target) {
|
||||
val lookupKey = byteSequence.obtainHeaderSignature() to target
|
||||
val scheme = schemes.computeIfAbsent(lookupKey) {
|
||||
registeredSchemes
|
||||
.filter { scheme -> scheme.canDeserializeVersion(it.first, it.second) }
|
||||
.forEach { return@computeIfAbsent it }
|
||||
logger.warn("Cannot find serialization scheme for: $lookupKey, registeredSchemes are: $registeredSchemes")
|
||||
NotSupportedSerializationScheme
|
||||
}
|
||||
return scheme to lookupKey.first
|
||||
}
|
||||
|
||||
@Throws(NotSerializableException::class)
|
||||
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
|
||||
return asCurrent { withCurrentContext(context) { schemeFor(byteSequence, context.useCase).deserialize(byteSequence, clazz, context) } }
|
||||
return asCurrent { withCurrentContext(context) { schemeFor(byteSequence, context.useCase).first.deserialize(byteSequence, clazz, context) } }
|
||||
}
|
||||
|
||||
@Throws(NotSerializableException::class)
|
||||
override fun <T : Any> deserializeWithCompatibleContext(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): ObjectWithCompatibleContext<T> {
|
||||
return asCurrent {
|
||||
withCurrentContext(context) {
|
||||
val (scheme, versionHeader) = schemeFor(byteSequence, context.useCase)
|
||||
val deserializedObject = scheme.deserialize(byteSequence, clazz, context)
|
||||
ObjectWithCompatibleContext(deserializedObject, context.withPreferredSerializationVersion(versionHeader))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
|
||||
return asCurrent { withCurrentContext(context) { schemeFor(context.preferredSerializationVersion, context.useCase).serialize(obj, context) } }
|
||||
return asCurrent { withCurrentContext(context) { schemeFor(context.preferredSerializationVersion, context.useCase).first.serialize(obj, context) } }
|
||||
}
|
||||
|
||||
fun registerScheme(scheme: SerializationScheme) {
|
||||
|
@ -18,8 +18,15 @@ import java.util.*
|
||||
import net.corda.nodeapi.internal.serialization.carpenter.Field as CarpenterField
|
||||
import net.corda.nodeapi.internal.serialization.carpenter.Schema as CarpenterSchema
|
||||
|
||||
// TODO: get an assigned number as per AMQP spec
|
||||
const val DESCRIPTOR_TOP_32BITS: Long = 0xc0da0000
|
||||
/**
|
||||
* R3 AMQP assigned enterprise number
|
||||
*
|
||||
* see [here](https://www.iana.org/assignments/enterprise-numbers/enterprise-numbers)
|
||||
*
|
||||
* Repeated here for brevity:
|
||||
* 50530 - R3 - Mike Hearn - mike&r3.com
|
||||
*/
|
||||
const val DESCRIPTOR_TOP_32BITS: Long = 0xc5620000
|
||||
|
||||
const val DESCRIPTOR_DOMAIN: String = "net.corda"
|
||||
|
||||
|
@ -0,0 +1,139 @@
|
||||
package net.corda.nodeapi
|
||||
|
||||
import net.corda.cordform.CordformNode
|
||||
import net.corda.testing.eventually
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import rx.schedulers.TestScheduler
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.time.Duration
|
||||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.streams.toList
|
||||
import kotlin.test.assertEquals
|
||||
|
||||
/**
|
||||
* tests for [NodeInfoFilesCopier]
|
||||
*/
|
||||
class NodeInfoFilesCopierTest {
|
||||
|
||||
@Rule @JvmField var folder = TemporaryFolder()
|
||||
private val rootPath get() = folder.root.toPath()
|
||||
private val scheduler = TestScheduler()
|
||||
companion object {
|
||||
private const val ORGANIZATION = "Organization"
|
||||
private const val NODE_1_PATH = "node1"
|
||||
private const val NODE_2_PATH = "node2"
|
||||
|
||||
private val content = "blah".toByteArray(Charsets.UTF_8)
|
||||
private val GOOD_NODE_INFO_NAME = "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}test"
|
||||
private val GOOD_NODE_INFO_NAME_2 = "${NodeInfoFilesCopier.NODE_INFO_FILE_NAME_PREFIX}anotherNode"
|
||||
private val BAD_NODE_INFO_NAME = "something"
|
||||
}
|
||||
|
||||
private fun nodeDir(nodeBaseDir : String) = rootPath.resolve(nodeBaseDir).resolve(ORGANIZATION.toLowerCase())
|
||||
|
||||
private val node1RootPath by lazy { nodeDir(NODE_1_PATH) }
|
||||
private val node2RootPath by lazy { nodeDir(NODE_2_PATH) }
|
||||
private val node1AdditionalNodeInfoPath by lazy { node1RootPath.resolve(CordformNode.NODE_INFO_DIRECTORY) }
|
||||
private val node2AdditionalNodeInfoPath by lazy { node2RootPath.resolve(CordformNode.NODE_INFO_DIRECTORY) }
|
||||
|
||||
lateinit var nodeInfoFilesCopier: NodeInfoFilesCopier
|
||||
|
||||
@Before
|
||||
fun setUp() {
|
||||
nodeInfoFilesCopier = NodeInfoFilesCopier(scheduler)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `files created before a node is started are copied to that node`() {
|
||||
// Configure the first node.
|
||||
nodeInfoFilesCopier.addConfig(node1RootPath)
|
||||
// Ensure directories are created.
|
||||
advanceTime()
|
||||
|
||||
// Create 2 files, a nodeInfo and another file in node1 folder.
|
||||
Files.write(node1RootPath.resolve(GOOD_NODE_INFO_NAME), content)
|
||||
Files.write(node1RootPath.resolve(BAD_NODE_INFO_NAME), content)
|
||||
|
||||
// Configure the second node.
|
||||
nodeInfoFilesCopier.addConfig(node2RootPath)
|
||||
advanceTime()
|
||||
|
||||
eventually<AssertionError, Unit>(Duration.ofMinutes(1)) {
|
||||
// Check only one file is copied.
|
||||
checkDirectoryContainsSingleFile(node2AdditionalNodeInfoPath, GOOD_NODE_INFO_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `polling of running nodes`() {
|
||||
// Configure 2 nodes.
|
||||
nodeInfoFilesCopier.addConfig(node1RootPath)
|
||||
nodeInfoFilesCopier.addConfig(node2RootPath)
|
||||
advanceTime()
|
||||
|
||||
// Create 2 files, one of which to be copied, in a node root path.
|
||||
Files.write(node2RootPath.resolve(GOOD_NODE_INFO_NAME), content)
|
||||
Files.write(node2RootPath.resolve(BAD_NODE_INFO_NAME), content)
|
||||
advanceTime()
|
||||
|
||||
eventually<AssertionError, Unit>(Duration.ofMinutes(1)) {
|
||||
// Check only one file is copied to the other node.
|
||||
checkDirectoryContainsSingleFile(node1AdditionalNodeInfoPath, GOOD_NODE_INFO_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `remove nodes`() {
|
||||
// Configure 2 nodes.
|
||||
nodeInfoFilesCopier.addConfig(node1RootPath)
|
||||
nodeInfoFilesCopier.addConfig(node2RootPath)
|
||||
advanceTime()
|
||||
|
||||
// Create a file, in node 2 root path.
|
||||
Files.write(node2RootPath.resolve(GOOD_NODE_INFO_NAME), content)
|
||||
advanceTime()
|
||||
|
||||
// Remove node 2
|
||||
nodeInfoFilesCopier.removeConfig(node2RootPath)
|
||||
|
||||
// Create another file in node 2 directory.
|
||||
Files.write(node2RootPath.resolve(GOOD_NODE_INFO_NAME_2), content)
|
||||
advanceTime()
|
||||
|
||||
eventually<AssertionError, Unit>(Duration.ofMinutes(1)) {
|
||||
// Check only one file is copied to the other node.
|
||||
checkDirectoryContainsSingleFile(node1AdditionalNodeInfoPath, GOOD_NODE_INFO_NAME)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clear`() {
|
||||
// Configure 2 nodes.
|
||||
nodeInfoFilesCopier.addConfig(node1RootPath)
|
||||
nodeInfoFilesCopier.addConfig(node2RootPath)
|
||||
advanceTime()
|
||||
|
||||
nodeInfoFilesCopier.reset()
|
||||
|
||||
advanceTime()
|
||||
Files.write(node2RootPath.resolve(GOOD_NODE_INFO_NAME_2), content)
|
||||
|
||||
// Give some time to the filesystem to report the change.
|
||||
Thread.sleep(100)
|
||||
assertEquals(0, Files.list(node1AdditionalNodeInfoPath).toList().size)
|
||||
}
|
||||
|
||||
private fun advanceTime() {
|
||||
scheduler.advanceTimeBy(1, TimeUnit.HOURS)
|
||||
}
|
||||
|
||||
private fun checkDirectoryContainsSingleFile(path: Path, filename: String) {
|
||||
assertEquals(1, Files.list(path).toList().size)
|
||||
val onlyFileName = Files.list(path).toList().first().fileName.toString()
|
||||
assertEquals(filename, onlyFileName)
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package net.corda.nodeapi.internal
|
||||
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.nhaarman.mockito_kotlin.doReturn
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import net.corda.core.contracts.*
|
||||
import net.corda.core.crypto.SecureHash
|
||||
@ -17,10 +17,7 @@ import net.corda.nodeapi.DummyContractBackdoor
|
||||
import net.corda.nodeapi.internal.serialization.SerializeAsTokenContextImpl
|
||||
import net.corda.nodeapi.internal.serialization.attachmentsClassLoaderEnabledPropertyName
|
||||
import net.corda.nodeapi.internal.serialization.withTokenContext
|
||||
import net.corda.testing.DUMMY_NOTARY
|
||||
import net.corda.testing.MEGA_CORP
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.kryoSpecific
|
||||
import net.corda.testing.*
|
||||
import net.corda.testing.node.MockAttachmentStorage
|
||||
import net.corda.testing.node.MockServices
|
||||
import org.apache.commons.io.IOUtils
|
||||
@ -41,8 +38,8 @@ class AttachmentsClassLoaderTests : TestDependencyInjectionBase() {
|
||||
private const val ISOLATED_CONTRACT_CLASS_NAME = "net.corda.finance.contracts.isolated.AnotherDummyContract"
|
||||
|
||||
private fun SerializationContext.withAttachmentStorage(attachmentStorage: AttachmentStorage): SerializationContext {
|
||||
val serviceHub = mock<ServiceHub>()
|
||||
whenever(serviceHub.attachments).thenReturn(attachmentStorage)
|
||||
val serviceHub = rigorousMock<ServiceHub>()
|
||||
doReturn(attachmentStorage).whenever(serviceHub).attachments
|
||||
return this.withServiceHub(serviceHub)
|
||||
}
|
||||
|
||||
|
@ -5,15 +5,13 @@ import com.esotericsoftware.kryo.io.Input
|
||||
import com.esotericsoftware.kryo.io.Output
|
||||
import com.esotericsoftware.kryo.util.DefaultClassResolver
|
||||
import com.esotericsoftware.kryo.util.MapReferenceResolver
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import com.nhaarman.mockito_kotlin.verify
|
||||
import com.nhaarman.mockito_kotlin.whenever
|
||||
import com.nhaarman.mockito_kotlin.*
|
||||
import net.corda.core.node.services.AttachmentStorage
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.utilities.ByteSequence
|
||||
import net.corda.nodeapi.internal.AttachmentsClassLoader
|
||||
import net.corda.nodeapi.internal.AttachmentsClassLoaderTests
|
||||
import net.corda.testing.node.MockAttachmentStorage
|
||||
import net.corda.testing.rigorousMock
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.ExpectedException
|
||||
@ -108,16 +106,6 @@ class CordaClassResolverTests {
|
||||
val emptyMapClass = mapOf<Any, Any>().javaClass
|
||||
}
|
||||
|
||||
val factory: SerializationFactory = object : SerializationFactory() {
|
||||
override fun <T : Any> deserialize(byteSequence: ByteSequence, clazz: Class<T>, context: SerializationContext): T {
|
||||
TODO("not implemented")
|
||||
}
|
||||
|
||||
override fun <T : Any> serialize(obj: T, context: SerializationContext): SerializedBytes<T> {
|
||||
TODO("not implemented")
|
||||
}
|
||||
}
|
||||
|
||||
private val emptyWhitelistContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, EmptyWhitelist, emptyMap(), true, SerializationContext.UseCase.P2P)
|
||||
private val allButBlacklistedContext: SerializationContext = SerializationContextImpl(KryoHeaderV0_1, this.javaClass.classLoader, AllButBlacklisted, emptyMap(), true, SerializationContext.UseCase.P2P)
|
||||
|
||||
@ -252,10 +240,11 @@ class CordaClassResolverTests {
|
||||
@Test
|
||||
fun `Kotlin EmptyList registers as Java emptyList`() {
|
||||
val javaEmptyListClass = Collections.emptyList<Any>().javaClass
|
||||
val kryo = mock<Kryo>()
|
||||
val kryo = rigorousMock<Kryo>()
|
||||
val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) }
|
||||
whenever(kryo.getDefaultSerializer(javaEmptyListClass)).thenReturn(DefaultSerializableSerializer())
|
||||
|
||||
doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptyListClass)
|
||||
doReturn(false).whenever(kryo).references
|
||||
doReturn(false).whenever(kryo).references = any()
|
||||
val registration = resolver.registerImplicit(emptyListClass)
|
||||
assertNotNull(registration)
|
||||
assertEquals(javaEmptyListClass, registration.type)
|
||||
@ -273,10 +262,11 @@ class CordaClassResolverTests {
|
||||
@Test
|
||||
fun `Kotlin EmptySet registers as Java emptySet`() {
|
||||
val javaEmptySetClass = Collections.emptySet<Any>().javaClass
|
||||
val kryo = mock<Kryo>()
|
||||
val kryo = rigorousMock<Kryo>()
|
||||
val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) }
|
||||
whenever(kryo.getDefaultSerializer(javaEmptySetClass)).thenReturn(DefaultSerializableSerializer())
|
||||
|
||||
doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptySetClass)
|
||||
doReturn(false).whenever(kryo).references
|
||||
doReturn(false).whenever(kryo).references = any()
|
||||
val registration = resolver.registerImplicit(emptySetClass)
|
||||
assertNotNull(registration)
|
||||
assertEquals(javaEmptySetClass, registration.type)
|
||||
@ -294,10 +284,11 @@ class CordaClassResolverTests {
|
||||
@Test
|
||||
fun `Kotlin EmptyMap registers as Java emptyMap`() {
|
||||
val javaEmptyMapClass = Collections.emptyMap<Any, Any>().javaClass
|
||||
val kryo = mock<Kryo>()
|
||||
val kryo = rigorousMock<Kryo>()
|
||||
val resolver = CordaClassResolver(allButBlacklistedContext).apply { setKryo(kryo) }
|
||||
whenever(kryo.getDefaultSerializer(javaEmptyMapClass)).thenReturn(DefaultSerializableSerializer())
|
||||
|
||||
doReturn(DefaultSerializableSerializer()).whenever(kryo).getDefaultSerializer(javaEmptyMapClass)
|
||||
doReturn(false).whenever(kryo).references
|
||||
doReturn(false).whenever(kryo).references = any()
|
||||
val registration = resolver.registerImplicit(emptyMapClass)
|
||||
assertNotNull(registration)
|
||||
assertEquals(javaEmptyMapClass, registration.type)
|
||||
|
@ -3,10 +3,10 @@ package net.corda.nodeapi.internal.serialization
|
||||
import com.esotericsoftware.kryo.Kryo
|
||||
import com.esotericsoftware.kryo.KryoException
|
||||
import com.esotericsoftware.kryo.io.Output
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import net.corda.core.serialization.*
|
||||
import net.corda.core.utilities.OpaqueBytes
|
||||
import net.corda.testing.TestDependencyInjectionBase
|
||||
import net.corda.testing.rigorousMock
|
||||
import org.assertj.core.api.Assertions.assertThat
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
@ -35,8 +35,7 @@ class SerializationTokenTest : TestDependencyInjectionBase() {
|
||||
override fun equals(other: Any?) = other is LargeTokenizable && other.bytes.size == this.bytes.size
|
||||
}
|
||||
|
||||
private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContextImpl(toBeTokenized, factory, context, mock())
|
||||
|
||||
private fun serializeAsTokenContext(toBeTokenized: Any) = SerializeAsTokenContextImpl(toBeTokenized, factory, context, rigorousMock())
|
||||
@Test
|
||||
fun `write token and read tokenizable`() {
|
||||
val tokenizableBefore = LargeTokenizable()
|
||||
|
@ -30,6 +30,7 @@ class EvolvabilityTests {
|
||||
// data class C (val a: Int, val b: Int)
|
||||
// val sc = SerializationOutput(sf).serialize(C(A, B))
|
||||
// f.writeBytes(sc.bytes)
|
||||
// println (path)
|
||||
|
||||
// new version of the class, in this case the order of the parameters has been swapped
|
||||
data class C(val b: Int, val a: Int)
|
||||
@ -54,6 +55,7 @@ class EvolvabilityTests {
|
||||
// data class C (val a: Int, val b: String)
|
||||
// val sc = SerializationOutput(sf).serialize(C(A, B))
|
||||
// f.writeBytes(sc.bytes)
|
||||
// println (path)
|
||||
|
||||
// new version of the class, in this case the order of the parameters has been swapped
|
||||
data class C(val b: String, val a: Int)
|
||||
@ -78,7 +80,6 @@ class EvolvabilityTests {
|
||||
// val sc = SerializationOutput(sf).serialize(C(A))
|
||||
// f.writeBytes(sc.bytes)
|
||||
// println ("Path = $path")
|
||||
|
||||
data class C(val a: Int, val b: Int?)
|
||||
|
||||
val sc2 = f.readBytes()
|
||||
@ -300,9 +301,6 @@ class EvolvabilityTests {
|
||||
val path2 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.2")
|
||||
val path3 = EvolvabilityTests::class.java.getResource("EvolvabilityTests.multiVersion.3")
|
||||
|
||||
@Suppress("UNUSED_VARIABLE")
|
||||
val f = File(path1.toURI())
|
||||
|
||||
val a = 100
|
||||
val b = 200
|
||||
val c = 300
|
||||
@ -312,14 +310,24 @@ class EvolvabilityTests {
|
||||
//
|
||||
// Version 1:
|
||||
// data class C (val a: Int, val b: Int)
|
||||
//
|
||||
// val scc = SerializationOutput(sf).serialize(C(a, b))
|
||||
// File(path1.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path1")
|
||||
//
|
||||
// Version 2 - add param c
|
||||
// data class C (val c: Int, val b: Int, val a: Int)
|
||||
//
|
||||
// val scc = SerializationOutput(sf).serialize(C(c, b, a))
|
||||
// File(path2.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path2")
|
||||
//
|
||||
// Version 3 - add param d
|
||||
// data class C (val b: Int, val c: Int, val d: Int, val a: Int)
|
||||
//
|
||||
// val scc = SerializationOutput(sf).serialize(C(b, c, d, a))
|
||||
// f.writeBytes(scc.bytes)
|
||||
// println ("Path = $path1")
|
||||
// File(path3.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path3")
|
||||
|
||||
@Suppress("UNUSED")
|
||||
data class C(val e: Int, val c: Int, val b: Int, val a: Int, val d: Int) {
|
||||
@ -409,14 +417,24 @@ class EvolvabilityTests {
|
||||
//
|
||||
// Version 1:
|
||||
// data class C (val a: Int, val b: Int, val c: Int)
|
||||
//
|
||||
// val scc = SerializationOutput(sf).serialize(C(a, b, c))
|
||||
// File(path1.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path1")
|
||||
//
|
||||
// Version 2 - add param c
|
||||
// data class C (val b: Int, val c: Int, val d: Int, val e: Int)
|
||||
//
|
||||
// val scc = SerializationOutput(sf).serialize(C(b, c, d, e))
|
||||
// File(path2.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path2")
|
||||
//
|
||||
// Version 3 - add param d
|
||||
// data class C (val b: Int, val c: Int, val d: Int, val e: Int, val f: Int)
|
||||
//
|
||||
// val scc = SerializationOutput(sf).serialize(C(b, c, d, e, f))
|
||||
// File(path1.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path1")
|
||||
// File(path3.toURI()).writeBytes(scc.bytes)
|
||||
// println ("Path = $path3")
|
||||
|
||||
@Suppress("UNUSED")
|
||||
data class C(val b: Int, val c: Int, val d: Int, val e: Int, val f: Int, val g: Int) {
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user