Fixed flows draining mode after regression introduced by OS -> ENT merge (#474)

This commit is contained in:
Michele Sollecito 2018-02-23 15:19:37 +00:00 committed by GitHub
parent 089916d350
commit 191f412aba
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 355 additions and 167 deletions

View File

@ -3906,6 +3906,7 @@ public static final class net.corda.testing.node.InMemoryMessagingNetwork$InMemo
@org.jetbrains.annotations.Nullable public final String component5() @org.jetbrains.annotations.Nullable public final String component5()
@org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$InMemoryMessage copy(String, net.corda.core.utilities.ByteSequence, net.corda.node.services.statemachine.DeduplicationId, java.time.Instant, String) @org.jetbrains.annotations.NotNull public final net.corda.testing.node.InMemoryMessagingNetwork$InMemoryMessage copy(String, net.corda.core.utilities.ByteSequence, net.corda.node.services.statemachine.DeduplicationId, java.time.Instant, String)
public boolean equals(Object) public boolean equals(Object)
@org.jetbrains.annotations.NotNull public Map getAdditionalHeaders()
@org.jetbrains.annotations.NotNull public net.corda.core.utilities.ByteSequence getData() @org.jetbrains.annotations.NotNull public net.corda.core.utilities.ByteSequence getData()
@org.jetbrains.annotations.NotNull public java.time.Instant getDebugTimestamp() @org.jetbrains.annotations.NotNull public java.time.Instant getDebugTimestamp()
@org.jetbrains.annotations.Nullable public String getSenderUUID() @org.jetbrains.annotations.Nullable public String getSenderUUID()
@ -3980,7 +3981,7 @@ public class net.corda.testing.node.MessagingServiceSpy extends java.lang.Object
public <init>(net.corda.node.services.messaging.MessagingService) public <init>(net.corda.node.services.messaging.MessagingService)
@org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.MessageHandlerRegistration addMessageHandler(String, kotlin.jvm.functions.Function3) @org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.MessageHandlerRegistration addMessageHandler(String, kotlin.jvm.functions.Function3)
public void cancelRedelivery(long) public void cancelRedelivery(long)
@org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.Message createMessage(String, byte[], net.corda.node.services.statemachine.DeduplicationId) @org.jetbrains.annotations.NotNull public net.corda.node.services.messaging.Message createMessage(String, byte[], net.corda.node.services.statemachine.DeduplicationId, Map)
@org.jetbrains.annotations.NotNull public net.corda.core.messaging.MessageRecipients getAddressOfParty(net.corda.core.node.services.PartyInfo) @org.jetbrains.annotations.NotNull public net.corda.core.messaging.MessageRecipients getAddressOfParty(net.corda.core.node.services.PartyInfo)
@org.jetbrains.annotations.NotNull public final net.corda.node.services.messaging.MessagingService getMessagingService() @org.jetbrains.annotations.NotNull public final net.corda.node.services.messaging.MessagingService getMessagingService()
@org.jetbrains.annotations.NotNull public net.corda.core.messaging.SingleMessageRecipient getMyAddress() @org.jetbrains.annotations.NotNull public net.corda.core.messaging.SingleMessageRecipient getMyAddress()

12
.idea/compiler.xml generated
View File

@ -10,6 +10,9 @@
<module name="bank-of-corda-demo_integrationTest" target="1.8" /> <module name="bank-of-corda-demo_integrationTest" target="1.8" />
<module name="bank-of-corda-demo_main" target="1.8" /> <module name="bank-of-corda-demo_main" target="1.8" />
<module name="bank-of-corda-demo_test" target="1.8" /> <module name="bank-of-corda-demo_test" target="1.8" />
<module name="behave_main" target="1.8" />
<module name="behave_scenario" target="1.8" />
<module name="behave_test" target="1.8" />
<module name="bootstrapper_main" target="1.8" /> <module name="bootstrapper_main" target="1.8" />
<module name="bootstrapper_test" target="1.8" /> <module name="bootstrapper_test" target="1.8" />
<module name="buildSrc_main" target="1.8" /> <module name="buildSrc_main" target="1.8" />
@ -27,6 +30,8 @@
<module name="confidential-identities_test" target="1.8" /> <module name="confidential-identities_test" target="1.8" />
<module name="corda-project_main" target="1.8" /> <module name="corda-project_main" target="1.8" />
<module name="corda-project_test" target="1.8" /> <module name="corda-project_test" target="1.8" />
<module name="cordapp-configuration_main" target="1.8" />
<module name="cordapp-configuration_test" target="1.8" />
<module name="cordapp_integrationTest" target="1.8" /> <module name="cordapp_integrationTest" target="1.8" />
<module name="cordapp_main" target="1.8" /> <module name="cordapp_main" target="1.8" />
<module name="cordapp_test" target="1.8" /> <module name="cordapp_test" target="1.8" />
@ -65,6 +70,7 @@
<module name="intellij-plugin_test" target="1.8" /> <module name="intellij-plugin_test" target="1.8" />
<module name="irs-demo_integrationTest" target="1.8" /> <module name="irs-demo_integrationTest" target="1.8" />
<module name="irs-demo_main" target="1.8" /> <module name="irs-demo_main" target="1.8" />
<module name="irs-demo_systemTest" target="1.8" />
<module name="irs-demo_test" target="1.8" /> <module name="irs-demo_test" target="1.8" />
<module name="isolated_main" target="1.8" /> <module name="isolated_main" target="1.8" />
<module name="isolated_test" target="1.8" /> <module name="isolated_test" target="1.8" />
@ -130,6 +136,12 @@
<module name="simm-valuation-demo_integrationTest" target="1.8" /> <module name="simm-valuation-demo_integrationTest" target="1.8" />
<module name="simm-valuation-demo_main" target="1.8" /> <module name="simm-valuation-demo_main" target="1.8" />
<module name="simm-valuation-demo_test" target="1.8" /> <module name="simm-valuation-demo_test" target="1.8" />
<module name="smoke-test-utils_main" target="1.8" />
<module name="smoke-test-utils_test" target="1.8" />
<module name="test-common_main" target="1.8" />
<module name="test-common_test" target="1.8" />
<module name="test-utils_main" target="1.8" />
<module name="test-utils_test" target="1.8" />
<module name="testing-smoke-test-utils_main" target="1.8" /> <module name="testing-smoke-test-utils_main" target="1.8" />
<module name="testing-smoke-test-utils_test" target="1.8" /> <module name="testing-smoke-test-utils_test" target="1.8" />
<module name="testing-test-common_main" target="1.8" /> <module name="testing-test-common_main" target="1.8" />

View File

@ -12,16 +12,27 @@ import net.corda.node.services.Permissions
import net.corda.node.services.Permissions.Companion.invokeRpc import net.corda.node.services.Permissions.Companion.invokeRpc
import net.corda.testing.core.* import net.corda.testing.core.*
import net.corda.testing.driver.DriverParameters import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.driver import net.corda.testing.driver.driver
import net.corda.testing.internal.IntegrationTest
import net.corda.testing.internal.IntegrationTestSchemas
import net.corda.testing.internal.toDatabaseSchemaName
import net.corda.testing.internal.toDatabaseSchemaNames
import net.corda.testing.node.User import net.corda.testing.node.User
import net.corda.testing.node.internal.NodeBasedTest import net.corda.testing.node.internal.NodeBasedTest
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.junit.Assume.assumeFalse import org.junit.Assume.assumeFalse
import org.junit.Before import org.junit.Before
import org.junit.ClassRule
import org.junit.Test import org.junit.Test
class FlowsExecutionModeRpcTest { class FlowsExecutionModeRpcTest : IntegrationTest() {
companion object {
@ClassRule
@JvmField
val databaseSchemas = IntegrationTestSchemas(*listOf(ALICE_NAME, BOB_NAME, DUMMY_BANK_A_NAME).map { it.toDatabaseSchemaNames("", "_10000", "_10003", "_10006") }.flatten().toTypedArray(), DUMMY_NOTARY_NAME.toDatabaseSchemaName())
}
@Test @Test
fun `persistent state survives node restart`() { fun `persistent state survives node restart`() {

View File

@ -1,7 +1,11 @@
package net.corda.node.modes.draining package net.corda.node.modes.draining
import co.paralleluniverse.fibers.Suspendable import co.paralleluniverse.fibers.Suspendable
import net.corda.core.flows.* import net.corda.core.flows.FlowLogic
import net.corda.core.flows.FlowSession
import net.corda.core.flows.InitiatedBy
import net.corda.core.flows.InitiatingFlow
import net.corda.core.flows.StartableByRPC
import net.corda.core.identity.Party import net.corda.core.identity.Party
import net.corda.core.internal.concurrent.map import net.corda.core.internal.concurrent.map
import net.corda.core.messaging.startFlow import net.corda.core.messaging.startFlow
@ -19,18 +23,18 @@ import org.junit.After
import org.junit.Before import org.junit.Before
import org.junit.Ignore import org.junit.Ignore
import org.junit.Test import org.junit.Test
import java.util.concurrent.ExecutorService
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.ScheduledExecutorService
import java.util.concurrent.TimeUnit
import kotlin.test.fail import kotlin.test.fail
@Ignore("Pending implementation")
class P2PFlowsDrainingModeTest { class P2PFlowsDrainingModeTest {
private val portAllocation = PortAllocation.Incremental(10000) private val portAllocation = PortAllocation.Incremental(10000)
private val user = User("mark", "dadada", setOf(Permissions.all())) private val user = User("mark", "dadada", setOf(Permissions.all()))
private val users = listOf(user) private val users = listOf(user)
private var executor: ExecutorService? = null private var executor: ScheduledExecutorService? = null
companion object { companion object {
private val logger = loggerFor<P2PFlowsDrainingModeTest>() private val logger = loggerFor<P2PFlowsDrainingModeTest>()
@ -38,7 +42,7 @@ class P2PFlowsDrainingModeTest {
@Before @Before
fun setup() { fun setup() {
executor = Executors.newSingleThreadExecutor() executor = Executors.newSingleThreadScheduledExecutor()
} }
@After @After
@ -49,7 +53,7 @@ class P2PFlowsDrainingModeTest {
@Test @Test
fun `flows draining mode suspends consumption of initial session messages`() { fun `flows draining mode suspends consumption of initial session messages`() {
driver(DriverParameters(isDebug = true, startNodesInProcess = false, portAllocation = portAllocation)) { driver(DriverParameters(isDebug = true, startNodesInProcess = true, portAllocation = portAllocation)) {
val initiatedNode = startNode().getOrThrow() val initiatedNode = startNode().getOrThrow()
val initiating = startNode(rpcUsers = users).getOrThrow().rpc val initiating = startNode(rpcUsers = users).getOrThrow().rpc
val counterParty = initiatedNode.nodeInfo.chooseIdentity() val counterParty = initiatedNode.nodeInfo.chooseIdentity()
@ -61,11 +65,11 @@ class P2PFlowsDrainingModeTest {
initiating.apply { initiating.apply {
val flow = startFlow(::InitiateSessionFlow, counterParty) val flow = startFlow(::InitiateSessionFlow, counterParty)
// this should be really fast, for the flow has already started, so 5 seconds should never be a problem // this should be really fast, for the flow has already started, so 5 seconds should never be a problem
executor!!.submit({ executor!!.schedule({
logger.info("Now disabling flows draining mode for $counterParty.") logger.info("Now disabling flows draining mode for $counterParty.")
shouldFail = false shouldFail = false
initiated.setFlowsDrainingModeEnabled(false) initiated.setFlowsDrainingModeEnabled(false)
}) }, 5, TimeUnit.SECONDS)
flow.returnValue.map { result -> flow.returnValue.map { result ->
if (shouldFail) { if (shouldFail) {
fail("Shouldn't happen until flows draining mode is switched off.") fail("Shouldn't happen until flows draining mode is switched off.")

View File

@ -193,8 +193,9 @@ open class Node(configuration: NodeConfiguration,
services.networkMapCache, services.networkMapCache,
services.monitoringService.metrics, services.monitoringService.metrics,
advertisedAddress, advertisedAddress,
networkParameters.maxMessageSize networkParameters.maxMessageSize,
) nodeProperties.flowsDrainingMode::isEnabled,
nodeProperties.flowsDrainingMode.values)
} }
private fun startLocalRpcBroker(networkParameters: NetworkParameters): BrokerAddresses? { private fun startLocalRpcBroker(networkParameters: NetworkParameters): BrokerAddresses? {

View File

@ -97,7 +97,7 @@ interface MessagingService {
* @param additionalProperties optional additional message headers. * @param additionalProperties optional additional message headers.
* @param topic identifier for the topic the message is sent to. * @param topic identifier for the topic the message is sent to.
*/ */
fun createMessage(topic: String, data: ByteArray, deduplicationId: DeduplicationId = DeduplicationId.createRandom(newSecureRandom())): Message fun createMessage(topic: String, data: ByteArray, deduplicationId: DeduplicationId = DeduplicationId.createRandom(newSecureRandom()), additionalHeaders: Map<String, String> = emptyMap()): Message
/** Given information about either a specific node or a service returns its corresponding address */ /** Given information about either a specific node or a service returns its corresponding address */
fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients
@ -106,9 +106,8 @@ interface MessagingService {
val myAddress: SingleMessageRecipient val myAddress: SingleMessageRecipient
} }
fun MessagingService.send(topicSession: String, payload: Any, to: MessageRecipients, deduplicationId: DeduplicationId = DeduplicationId.createRandom(newSecureRandom()), retryId: Long? = null, additionalHeaders: Map<String, String> = emptyMap())
fun MessagingService.send(topicSession: String, payload: Any, to: MessageRecipients, deduplicationId: DeduplicationId = DeduplicationId.createRandom(newSecureRandom()), retryId: Long? = null) = send(createMessage(topicSession, payload.serialize().bytes, deduplicationId, additionalHeaders), to, retryId)
= send(createMessage(topicSession, payload.serialize().bytes, deduplicationId), to, retryId)
interface MessageHandlerRegistration interface MessageHandlerRegistration
@ -129,6 +128,7 @@ interface Message {
val debugTimestamp: Instant val debugTimestamp: Instant
val uniqueMessageId: DeduplicationId val uniqueMessageId: DeduplicationId
val senderUUID: String? val senderUUID: String?
val additionalHeaders: Map<String, String>
} }
// TODO Have ReceivedMessage point to the TLS certificate of the peer, and [peer] would simply be the subject DN of that. // TODO Have ReceivedMessage point to the TLS certificate of the peer, and [peer] would simply be the subject DN of that.
@ -165,3 +165,11 @@ interface AcknowledgeHandle {
} }
typealias MessageHandler = (ReceivedMessage, MessageHandlerRegistration, AcknowledgeHandle) -> Unit typealias MessageHandler = (ReceivedMessage, MessageHandlerRegistration, AcknowledgeHandle) -> Unit
object P2PMessagingHeaders {
object Type {
const val KEY = "corda_p2p_message_type"
const val SESSION_INIT_VALUE = "session_init"
}
}

View File

@ -223,6 +223,7 @@ class MessagingExecutor(
if (amqDelayMillis > 0 && message.topic == FlowMessagingImpl.sessionTopic) { if (amqDelayMillis > 0 && message.topic == FlowMessagingImpl.sessionTopic) {
putLongProperty(org.apache.activemq.artemis.api.core.Message.HDR_SCHEDULED_DELIVERY_TIME, System.currentTimeMillis() + amqDelayMillis) putLongProperty(org.apache.activemq.artemis.api.core.Message.HDR_SCHEDULED_DELIVERY_TIME, System.currentTimeMillis() + amqDelayMillis)
} }
message.additionalHeaders.forEach { key, value -> putStringProperty(key, value) }
} }
} }

View File

@ -14,19 +14,31 @@ import net.corda.core.node.services.PartyInfo
import net.corda.core.serialization.SerializationDefaults import net.corda.core.serialization.SerializationDefaults
import net.corda.core.serialization.SingletonSerializeAsToken import net.corda.core.serialization.SingletonSerializeAsToken
import net.corda.core.serialization.deserialize import net.corda.core.serialization.deserialize
import net.corda.core.serialization.internal.nodeSerializationEnv
import net.corda.core.serialization.serialize import net.corda.core.serialization.serialize
import net.corda.core.utilities.* import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.trace
import net.corda.node.VersionInfo import net.corda.node.VersionInfo
import net.corda.node.internal.LifecycleSupport
import net.corda.node.internal.artemis.ReactiveArtemisConsumer.Companion.multiplex
import net.corda.node.services.api.NetworkMapCacheInternal import net.corda.node.services.api.NetworkMapCacheInternal
import net.corda.node.services.config.NodeConfiguration import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.statemachine.DeduplicationId import net.corda.node.services.statemachine.DeduplicationId
import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.PersistentMap import net.corda.node.utilities.PersistentMap
import net.corda.nodeapi.internal.ArtemisMessagingClient import net.corda.nodeapi.ArtemisTcpTransport
import net.corda.nodeapi.internal.ArtemisMessagingComponent.* import net.corda.nodeapi.ConnectionDirection
import net.corda.nodeapi.internal.ArtemisMessagingComponent
import net.corda.nodeapi.internal.ArtemisMessagingComponent.ArtemisAddress
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_CONTROL
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_NOTIFY import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.BRIDGE_NOTIFY
import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX import net.corda.nodeapi.internal.ArtemisMessagingComponent.Companion.PEERS_PREFIX
import net.corda.nodeapi.internal.ArtemisMessagingComponent.NodeAddress
import net.corda.nodeapi.internal.ArtemisMessagingComponent.RemoteInboxAddress
import net.corda.nodeapi.internal.ArtemisMessagingComponent.ServiceAddress
import net.corda.nodeapi.internal.bridging.BridgeControl import net.corda.nodeapi.internal.bridging.BridgeControl
import net.corda.nodeapi.internal.bridging.BridgeEntry import net.corda.nodeapi.internal.bridging.BridgeEntry
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -36,11 +48,16 @@ import org.apache.activemq.artemis.api.core.Message.HDR_DUPLICATE_DETECTION_ID
import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER
import org.apache.activemq.artemis.api.core.RoutingType import org.apache.activemq.artemis.api.core.RoutingType
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.ActiveMQClient
import org.apache.activemq.artemis.api.core.client.ClientConsumer import org.apache.activemq.artemis.api.core.client.ClientConsumer
import org.apache.activemq.artemis.api.core.client.ClientMessage import org.apache.activemq.artemis.api.core.client.ClientMessage
import org.apache.activemq.artemis.api.core.client.ClientProducer
import org.apache.activemq.artemis.api.core.client.ClientSession import org.apache.activemq.artemis.api.core.client.ClientSession
import org.apache.activemq.artemis.api.core.client.ServerLocator
import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY import org.apache.commons.lang.ArrayUtils.EMPTY_BYTE_ARRAY
import rx.Observable
import rx.Subscription import rx.Subscription
import rx.subjects.PublishSubject
import java.security.PublicKey import java.security.PublicKey
import java.time.Instant import java.time.Instant
import java.util.* import java.util.*
@ -82,7 +99,7 @@ import javax.persistence.Lob
@ThreadSafe @ThreadSafe
class P2PMessagingClient(val config: NodeConfiguration, class P2PMessagingClient(val config: NodeConfiguration,
private val versionInfo: VersionInfo, private val versionInfo: VersionInfo,
serverAddress: NetworkHostAndPort, private val serverAddress: NetworkHostAndPort,
private val myIdentity: PublicKey, private val myIdentity: PublicKey,
private val serviceIdentity: PublicKey?, private val serviceIdentity: PublicKey?,
private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor, private val nodeExecutor: AffinityExecutor.ServiceAffinityExecutor,
@ -90,8 +107,10 @@ class P2PMessagingClient(val config: NodeConfiguration,
private val networkMap: NetworkMapCacheInternal, private val networkMap: NetworkMapCacheInternal,
private val metricRegistry: MetricRegistry, private val metricRegistry: MetricRegistry,
advertisedAddress: NetworkHostAndPort = serverAddress, advertisedAddress: NetworkHostAndPort = serverAddress,
maxMessageSize: Int private val maxMessageSize: Int,
) : SingletonSerializeAsToken(), MessagingService, AddressToArtemisQueueResolver { private val isDrainingModeOn: () -> Boolean,
private val drainingModeWasChangedEvents: Observable<Pair<Boolean, Boolean>>
) : SingletonSerializeAsToken(), MessagingService, AddressToArtemisQueueResolver, AutoCloseable {
companion object { companion object {
private val log = contextLogger() private val log = contextLogger()
// This is a "property" attached to an Artemis MQ message object, which contains our own notion of "topic". // This is a "property" attached to an Artemis MQ message object, which contains our own notion of "topic".
@ -105,7 +124,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
val senderUUID = SimpleString("sender-uuid") val senderUUID = SimpleString("sender-uuid")
val senderSeqNo = SimpleString("send-seq-no") val senderSeqNo = SimpleString("send-seq-no")
private val messageMaxRetryCount: Int = 3 private const val messageMaxRetryCount: Int = 3
fun createMessageToRedeliver(): PersistentMap<Long, Pair<Message, MessageRecipients>, RetryMessage, Long> { fun createMessageToRedeliver(): PersistentMap<Long, Pair<Message, MessageRecipients>, RetryMessage, Long> {
return PersistentMap( return PersistentMap(
@ -127,18 +146,25 @@ class P2PMessagingClient(val config: NodeConfiguration,
) )
} }
private class NodeClientMessage(override val topic: String, override val data: ByteSequence, override val uniqueMessageId: DeduplicationId, override val senderUUID: String?) : Message { private class NodeClientMessage(override val topic: String, override val data: ByteSequence, override val uniqueMessageId: DeduplicationId, override val senderUUID: String?, override val additionalHeaders: Map<String, String>) : Message {
override val debugTimestamp: Instant = Instant.now() override val debugTimestamp: Instant = Instant.now()
override fun toString() = "$topic#${String(data.bytes)}" override fun toString() = "$topic#${String(data.bytes)}"
} }
} }
private class InnerState { private class InnerState {
var started = false
var running = false var running = false
var p2pConsumer: ClientConsumer? = null var eventsSubscription: Subscription? = null
var serviceConsumer: ClientConsumer? = null var p2pConsumer: P2PMessagingConsumer? = null
var locator: ServerLocator? = null
var producer: ClientProducer? = null
var producerSession: ClientSession? = null
var bridgeSession: ClientSession? = null
var bridgeNotifyConsumer: ClientConsumer? = null var bridgeNotifyConsumer: ClientConsumer? = null
var networkChangeSubscription: Subscription? = null var networkChangeSubscription: Subscription? = null
fun sendMessage(address: String, message: ClientMessage) = producer!!.send(address, message)
} }
private val messagesToRedeliver = database.transaction { private val messagesToRedeliver = database.transaction {
@ -152,14 +178,6 @@ class P2PMessagingClient(val config: NodeConfiguration,
override val myAddress: SingleMessageRecipient = NodeAddress(myIdentity, advertisedAddress) override val myAddress: SingleMessageRecipient = NodeAddress(myIdentity, advertisedAddress)
private val messageRedeliveryDelaySeconds = config.messageRedeliveryDelaySeconds.toLong() private val messageRedeliveryDelaySeconds = config.messageRedeliveryDelaySeconds.toLong()
private val artemis = ArtemisMessagingClient(
config = config,
serverAddress = serverAddress,
maxMessageSize = maxMessageSize,
autoCommitSends = false,
autoCommitAcks = false,
confirmationWindowSize = config.enterpriseConfiguration.tuning.p2pConfirmationWindowSize
)
private val state = ThreadBox(InnerState()) private val state = ThreadBox(InnerState())
private val knownQueues = Collections.newSetFromMap(ConcurrentHashMap<String, Boolean>()) private val knownQueues = Collections.newSetFromMap(ConcurrentHashMap<String, Boolean>())
@ -185,28 +203,46 @@ class P2PMessagingClient(val config: NodeConfiguration,
fun start() { fun start() {
state.locked { state.locked {
val started = artemis.start() started = true
val session = started.session log.info("Connecting to message broker: $serverAddress")
val inbox = RemoteInboxAddress(myIdentity).queueName // TODO Add broker CN to config for host verification in case the embedded broker isn't used
val inboxes = mutableListOf(inbox) val tcpTransport = ArtemisTcpTransport.tcpTransport(ConnectionDirection.Outbound(), serverAddress, config)
// Create a queue, consumer and producer for handling P2P network messages. locator = ActiveMQClient.createServerLocatorWithoutHA(tcpTransport).apply {
createQueueIfAbsent(inbox) // Never time out on our loopback Artemis connections. If we switch back to using the InVM transport this
p2pConsumer = session.createConsumer(inbox) // would be the default and the two lines below can be deleted.
if (serviceIdentity != null) { connectionTTL = -1
val serviceAddress = RemoteInboxAddress(serviceIdentity).queueName clientFailureCheckPeriod = -1
inboxes += serviceAddress minLargeMessageSize = maxMessageSize
createQueueIfAbsent(serviceAddress) isUseGlobalPools = nodeSerializationEnv != null
val serviceHandler = session.createConsumer(serviceAddress)
serviceHandler.setMessageHandler { msg ->
val message: ReceivedMessage? = artemisToCordaMessage(msg)
if (message != null)
deliver(msg, message)
} }
val sessionFactory = locator!!.createSessionFactory()
// Login using the node username. The broker will authenticate us as its node (as opposed to another peer)
// using our TLS certificate.
// Note that the acknowledgement of messages is not flushed to the Artermis journal until the default buffer
// size of 1MB is acknowledged.
val createNewSession = { sessionFactory!!.createSession(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER, false, true, true, locator!!.isPreAcknowledge, ActiveMQClient.DEFAULT_ACK_BATCH_SIZE) }
producerSession = createNewSession()
bridgeSession = createNewSession()
producerSession!!.start()
bridgeSession!!.start()
val inboxes = mutableSetOf<String>()
// Create a queue, consumer and producer for handling P2P network messages.
// Create a general purpose producer.
producer = producerSession!!.createProducer()
inboxes += RemoteInboxAddress(myIdentity).queueName
serviceIdentity?.let {
inboxes += RemoteInboxAddress(it).queueName
} }
inboxes.forEach { createQueueIfAbsent(it, producerSession!!) }
p2pConsumer = P2PMessagingConsumer(inboxes, createNewSession, isDrainingModeOn, drainingModeWasChangedEvents)
val messagingExecutor = MessagingExecutor( val messagingExecutor = MessagingExecutor(
session, producerSession!!,
started.producer, producer!!,
versionInfo, versionInfo,
this@P2PMessagingClient, this@P2PMessagingClient,
metricRegistry, metricRegistry,
@ -216,8 +252,8 @@ class P2PMessagingClient(val config: NodeConfiguration,
this@P2PMessagingClient.messagingExecutor = messagingExecutor this@P2PMessagingClient.messagingExecutor = messagingExecutor
messagingExecutor.start() messagingExecutor.start()
registerBridgeControl(session, inboxes) registerBridgeControl(bridgeSession!!, inboxes.toList())
enumerateBridges(session, inboxes) enumerateBridges(bridgeSession!!, inboxes.toList())
} }
resumeMessageRedelivery() resumeMessageRedelivery()
@ -237,7 +273,7 @@ class P2PMessagingClient(val config: NodeConfiguration,
log.info(notifyMessage.toString()) log.info(notifyMessage.toString())
when (notifyMessage) { when (notifyMessage) {
is BridgeControl.BridgeToNodeSnapshotRequest -> enumerateBridges(session, inboxes) is BridgeControl.BridgeToNodeSnapshotRequest -> enumerateBridges(session, inboxes)
else -> log.error("Unexpected Bridge Control message type on notify topc $notifyMessage") else -> log.error("Unexpected Bridge Control message type on notify topic $notifyMessage")
} }
msg.acknowledge() msg.acknowledge()
} }
@ -246,21 +282,23 @@ class P2PMessagingClient(val config: NodeConfiguration,
} }
private fun sendBridgeControl(message: BridgeControl) { private fun sendBridgeControl(message: BridgeControl) {
val client = artemis.started!! state.locked {
val controlPacket = message.serialize(context = SerializationDefaults.P2P_CONTEXT).bytes val controlPacket = message.serialize(context = SerializationDefaults.P2P_CONTEXT).bytes
val artemisMessage = client.session.createMessage(false) val artemisMessage = producerSession!!.createMessage(false)
artemisMessage.writeBodyBufferBytes(controlPacket) artemisMessage.writeBodyBufferBytes(controlPacket)
client.producer.send(BRIDGE_CONTROL, artemisMessage) sendMessage(BRIDGE_CONTROL, artemisMessage)
client.session.commit() }
} }
private fun updateBridgesOnNetworkChange(change: NetworkMapCache.MapChange) { private fun updateBridgesOnNetworkChange(change: NetworkMapCache.MapChange) {
log.info("Updating bridges on network map change: ${change.node}") log.info("Updating bridges on network map change: ${change.node}")
fun gatherAddresses(node: NodeInfo): Sequence<BridgeEntry> { fun gatherAddresses(node: NodeInfo): Sequence<BridgeEntry> {
return node.legalIdentitiesAndCerts.map { return state.locked {
node.legalIdentitiesAndCerts.map {
val messagingAddress = NodeAddress(it.party.owningKey, node.addresses.first()) val messagingAddress = NodeAddress(it.party.owningKey, node.addresses.first())
BridgeEntry(messagingAddress.queueName, node.addresses, node.legalIdentities.map { it.name }) BridgeEntry(messagingAddress.queueName, node.addresses, node.legalIdentities.map { it.name })
}.filter { artemis.started!!.session.queueQuery(SimpleString(it.queueName)).isExists }.asSequence() }.filter { producerSession!!.queueQuery(SimpleString(it.queueName)).isExists }.asSequence()
}
} }
fun deployBridges(node: NodeInfo) { fun deployBridges(node: NodeInfo) {
@ -319,39 +357,31 @@ class P2PMessagingClient(val config: NodeConfiguration,
private val shutdownLatch = CountDownLatch(1) private val shutdownLatch = CountDownLatch(1)
private fun processMessage(consumer: ClientConsumer): Boolean {
// Two possibilities here:
//
// 1. We block waiting for a message and the consumer is closed in another thread. In this case
// receive returns null and we break out of the loop.
// 2. We receive a message and process it, and stop() is called during delivery. In this case,
// calling receive will throw and we break out of the loop.
//
// It's safe to call into receive simultaneous with other threads calling send on a producer.
val artemisMessage: ClientMessage = try {
consumer.receive()
} catch (e: ActiveMQObjectClosedException) {
null
} ?: return false
deliver(artemisMessage)
return true
}
/** /**
* Starts the p2p event loop: this method only returns once [stop] has been called. * Starts the p2p event loop: this method only returns once [stop] has been called.
*/ */
fun run() { fun run() {
val latch = CountDownLatch(1)
try { try {
val consumer = state.locked { val consumer = state.locked {
check(artemis.started != null) { "start must be called first" } check(started) { "start must be called first" }
check(!running) { "run can't be called twice" } check(!running) { "run can't be called twice" }
running = true running = true
// If it's null, it means we already called stop, so return immediately. // If it's null, it means we already called stop, so return immediately.
p2pConsumer ?: return if (p2pConsumer == null) {
return
} }
eventsSubscription = p2pConsumer!!.messages
while (processMessage(consumer)) { } .doOnError { error -> throw error }
.doOnNext { message -> deliver(message) }
// this `run()` method is semantically meant to block until the message consumption runs, hence the latch here
.doOnCompleted(latch::countDown)
.doOnError { error -> throw error }
.subscribe()
p2pConsumer!!
}
consumer.start()
latch.await()
} finally { } finally {
shutdownLatch.countDown() shutdownLatch.countDown()
} }
@ -389,35 +419,42 @@ class P2PMessagingClient(val config: NodeConfiguration,
private val message: ClientMessage) : ReceivedMessage { private val message: ClientMessage) : ReceivedMessage {
override val data: ByteSequence by lazy { OpaqueBytes(ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }) } override val data: ByteSequence by lazy { OpaqueBytes(ByteArray(message.bodySize).apply { message.bodyBuffer.readBytes(this) }) }
override val debugTimestamp: Instant get() = Instant.ofEpochMilli(message.timestamp) override val debugTimestamp: Instant get() = Instant.ofEpochMilli(message.timestamp)
override val additionalHeaders: Map<String, String> = emptyMap()
override fun toString() = "$topic#$data" override fun toString() = "$topic#$data"
} }
internal fun deliver(artemisMessage: ClientMessage) { internal fun deliver(artemisMessage: ClientMessage) {
val message: ReceivedMessage? = artemisToCordaMessage(artemisMessage)
if (message != null) artemisToCordaMessage(artemisMessage)?.let { cordaMessage ->
deliver(artemisMessage, message) if (!deduplicator.isDuplicate(cordaMessage)) {
deliver(cordaMessage, artemisMessage)
} else {
log.trace { "Discard duplicate message ${cordaMessage.uniqueMessageId} for ${cordaMessage.topic}" }
}
}
} }
private fun deliver(artemisMessage: ClientMessage, msg: ReceivedMessage) { private fun deliver(msg: ReceivedMessage, artemisMessage: ClientMessage) {
state.checkNotLocked() state.checkNotLocked()
val deliverTo = handlers[msg.topic] val deliverTo = handlers[msg.topic]
try {
// This will perform a BLOCKING call onto the executor. Thus if the handlers are slow, we will
// be slow, and Artemis can handle that case intelligently. We don't just invoke the handler
// directly in order to ensure that we have the features of the AffinityExecutor class throughout
// the bulk of the codebase and other non-messaging jobs can be scheduled onto the server executor
// easily.
//
// Note that handlers may re-enter this class. We aren't holding any locks and methods like
// start/run/stop have re-entrancy assertions at the top, so it is OK.
if (deliverTo != null) { if (deliverTo != null) {
if (deduplicator.isDuplicate(msg)) { try {
log.trace { "Discard duplicate message ${msg.uniqueMessageId} for ${msg.topic}" } deliverTo(msg, HandlerRegistration(msg.topic, deliverTo), acknowledgeHandleFor(artemisMessage, msg))
return } catch (e: Exception) {
log.error("Caught exception whilst executing message handler for ${msg.topic}", e)
} }
val acknowledgeHandle = object : AcknowledgeHandle { } else {
log.warn("Received message ${msg.uniqueMessageId} for ${msg.topic} that doesn't have any registered handlers yet")
}
}
private fun acknowledgeHandleFor(artemisMessage: ClientMessage, cordaMessage: ReceivedMessage): AcknowledgeHandle {
return object : AcknowledgeHandle {
override fun persistDeduplicationId() { override fun persistDeduplicationId() {
deduplicator.persistDeduplicationId(msg) deduplicator.persistDeduplicationId(cordaMessage)
} }
// ACKing a message calls back into the session which isn't thread safe, so we have to ensure it // ACKing a message calls back into the session which isn't thread safe, so we have to ensure it
@ -428,13 +465,6 @@ class P2PMessagingClient(val config: NodeConfiguration,
messagingExecutor!!.acknowledge(artemisMessage) messagingExecutor!!.acknowledge(artemisMessage)
} }
} }
deliverTo(msg, HandlerRegistration(msg.topic, deliverTo), acknowledgeHandle)
} else {
log.warn("Received message ${msg.uniqueMessageId} for ${msg.topic} that doesn't have any registered handlers yet")
}
} catch (e: Exception) {
log.error("Caught exception whilst executing message handler for ${msg.topic}", e)
}
} }
/** /**
@ -446,30 +476,24 @@ class P2PMessagingClient(val config: NodeConfiguration,
fun stop() { fun stop() {
val running = state.locked { val running = state.locked {
// We allow stop() to be called without a run() in between, but it must have at least been started. // We allow stop() to be called without a run() in between, but it must have at least been started.
check(artemis.started != null) check(started)
val prevRunning = running val prevRunning = running
running = false running = false
networkChangeSubscription?.unsubscribe() networkChangeSubscription?.unsubscribe()
val c = p2pConsumer ?: throw IllegalStateException("stop can't be called twice") require(p2pConsumer != null, {"stop can't be called twice"})
try { require(producer != null, {"stop can't be called twice"})
c.close()
} catch (e: ActiveMQObjectClosedException) { close(p2pConsumer)
// Ignore it: this can happen if the server has gone away before we do.
}
try {
bridgeNotifyConsumer!!.close()
} catch (e: ActiveMQObjectClosedException) {
// Ignore it: this can happen if the server has gone away before we do.
}
p2pConsumer = null p2pConsumer = null
val s = serviceConsumer
try { close(producer)
s?.close() producer = null
} catch (e: ActiveMQObjectClosedException) { producerSession!!.commit()
// Ignore it: this can happen if the server has gone away before we do.
} close(bridgeNotifyConsumer)
serviceConsumer = null
knownQueues.clear() knownQueues.clear()
eventsSubscription?.unsubscribe()
eventsSubscription = null
prevRunning prevRunning
} }
if (running && !nodeExecutor.isOnThread) { if (running && !nodeExecutor.isOnThread) {
@ -478,12 +502,20 @@ class P2PMessagingClient(val config: NodeConfiguration,
} }
// Only first caller to gets running true to protect against double stop, which seems to happen in some integration tests. // Only first caller to gets running true to protect against double stop, which seems to happen in some integration tests.
messagingExecutor?.close() messagingExecutor?.close()
if (running) {
state.locked { state.locked {
artemis.stop() locator?.close()
} }
} }
private fun close(target: AutoCloseable?) {
try {
target?.close()
} catch (ignored: ActiveMQObjectClosedException) {
// swallow
} }
}
override fun close() = stop()
@Suspendable @Suspendable
override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any) { override fun send(message: Message, target: MessageRecipients, retryId: Long?, sequenceKey: Any) {
@ -495,7 +527,6 @@ class P2PMessagingClient(val config: NodeConfiguration,
scheduledMessageRedeliveries[it] = nodeExecutor.schedule({ scheduledMessageRedeliveries[it] = nodeExecutor.schedule({
sendWithRetry(0, message, target, retryId) sendWithRetry(0, message, target, retryId)
}, messageRedeliveryDelaySeconds, TimeUnit.SECONDS) }, messageRedeliveryDelaySeconds, TimeUnit.SECONDS)
} }
} }
@ -544,15 +575,16 @@ class P2PMessagingClient(val config: NodeConfiguration,
// Otherwise we send the message to an internal queue for the target residing on our broker. It's then the // Otherwise we send the message to an internal queue for the target residing on our broker. It's then the
// broker's job to route the message to the target's P2P queue. // broker's job to route the message to the target's P2P queue.
val internalTargetQueue = (address as? ArtemisAddress)?.queueName ?: throw IllegalArgumentException("Not an Artemis address") val internalTargetQueue = (address as? ArtemisAddress)?.queueName ?: throw IllegalArgumentException("Not an Artemis address")
createQueueIfAbsent(internalTargetQueue) state.locked {
createQueueIfAbsent(internalTargetQueue, producerSession!!)
}
internalTargetQueue internalTargetQueue
} }
} }
/** Attempts to create a durable queue on the broker which is bound to an address of the same name. */ /** Attempts to create a durable queue on the broker which is bound to an address of the same name. */
private fun createQueueIfAbsent(queueName: String) { private fun createQueueIfAbsent(queueName: String, session: ClientSession) {
if (!knownQueues.contains(queueName)) { if (!knownQueues.contains(queueName)) {
val session = artemis.started!!.session
val queueQuery = session.queueQuery(SimpleString(queueName)) val queueQuery = session.queueQuery(SimpleString(queueName))
if (!queueQuery.isExists) { if (!queueQuery.isExists) {
log.info("Create fresh queue $queueName bound on same address") log.info("Create fresh queue $queueName bound on same address")
@ -587,8 +619,9 @@ class P2PMessagingClient(val config: NodeConfiguration,
handlers.remove(registration.topic) handlers.remove(registration.topic)
} }
override fun createMessage(topic: String, data: ByteArray, deduplicationId: DeduplicationId): Message { override fun createMessage(topic: String, data: ByteArray, deduplicationId: DeduplicationId, additionalHeaders: Map<String, String>): Message {
return NodeClientMessage(topic, OpaqueBytes(data), deduplicationId, deduplicator.ourSenderUUID)
return NodeClientMessage(topic, OpaqueBytes(data), deduplicationId, deduplicator.ourSenderUUID, additionalHeaders)
} }
override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients { override fun getAddressOfParty(partyInfo: PartyInfo): MessageRecipients {
@ -598,3 +631,78 @@ class P2PMessagingClient(val config: NodeConfiguration,
} }
} }
} }
private class P2PMessagingConsumer(
queueNames: Set<String>,
createSession: () -> ClientSession,
private val isDrainingModeOn: () -> Boolean,
private val drainingModeWasChangedEvents: Observable<Pair<Boolean, Boolean>>) : LifecycleSupport {
private companion object {
private const val initialSessionMessages = "${P2PMessagingHeaders.Type.KEY}='${P2PMessagingHeaders.Type.SESSION_INIT_VALUE}'"
private const val existingSessionMessages = "${P2PMessagingHeaders.Type.KEY}<>'${P2PMessagingHeaders.Type.SESSION_INIT_VALUE}'"
}
private var startedFlag = false
val messages: PublishSubject<ClientMessage> = PublishSubject.create<ClientMessage>()
private var initialConsumer = multiplex(queueNames, createSession, initialSessionMessages)
private var existingConsumer = multiplex(queueNames, createSession, existingSessionMessages)
private val subscriptions = mutableSetOf<Subscription>()
override fun start() {
synchronized(this) {
require(!startedFlag)
drainingModeWasChangedEvents.filter { change -> change.switchedOn() }.doOnNext { pauseInitial() }.subscribe()
drainingModeWasChangedEvents.filter { change -> change.switchedOff() }.doOnNext { resumeInitial() }.subscribe()
subscriptions += initialConsumer.messages.doOnNext(messages::onNext).subscribe()
subscriptions += existingConsumer.messages.doOnNext(messages::onNext).subscribe()
if (!isDrainingModeOn()) {
initialConsumer.start()
}
existingConsumer.start()
startedFlag = true
}
}
override fun stop() {
synchronized(this) {
if (startedFlag) {
initialConsumer.stop()
existingConsumer.stop()
subscriptions.forEach(Subscription::unsubscribe)
subscriptions.clear()
startedFlag = false
}
messages.onCompleted()
}
}
override val started: Boolean
get() = startedFlag
private fun pauseInitial() {
if (initialConsumer.started && initialConsumer.connected) {
initialConsumer.disconnect()
}
}
private fun resumeInitial() {
if(!initialConsumer.started) {
initialConsumer.start()
}
if (!initialConsumer.connected) {
initialConsumer.connect()
}
}
private fun Pair<Boolean, Boolean>.switchedOff() = first && !second
private fun Pair<Boolean, Boolean>.switchedOn() = !first && second
}

View File

@ -29,10 +29,10 @@ class NodePropertiesPersistentStore(readPhysicalNodeId: () -> String, persistenc
@Table(name = "${NODE_DATABASE_PREFIX}properties") @Table(name = "${NODE_DATABASE_PREFIX}properties")
class DBNodeProperty( class DBNodeProperty(
@Id @Id
@Column(name = "key") @Column(name = "property_key")
val key: String = "", val key: String = "",
@Column(name = "value") @Column(name = "property_value")
var value: String? = "" var value: String? = ""
) )
} }

View File

@ -10,6 +10,7 @@ import net.corda.core.utilities.contextLogger
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.node.services.api.ServiceHubInternal import net.corda.node.services.api.ServiceHubInternal
import net.corda.node.services.messaging.AcknowledgeHandle import net.corda.node.services.messaging.AcknowledgeHandle
import net.corda.node.services.messaging.P2PMessagingHeaders
import net.corda.node.services.messaging.ReceivedMessage import net.corda.node.services.messaging.ReceivedMessage
import java.io.NotSerializableException import java.io.NotSerializableException
@ -50,7 +51,7 @@ class FlowMessagingImpl(val serviceHub: ServiceHubInternal): FlowMessaging {
@Suspendable @Suspendable
override fun sendSessionMessage(party: Party, message: SessionMessage, deduplicationId: DeduplicationId) { override fun sendSessionMessage(party: Party, message: SessionMessage, deduplicationId: DeduplicationId) {
log.trace { "Sending message $deduplicationId $message to party $party" } log.trace { "Sending message $deduplicationId $message to party $party" }
val networkMessage = serviceHub.networkService.createMessage(sessionTopic, serializeSessionMessage(message).bytes, deduplicationId) val networkMessage = serviceHub.networkService.createMessage(sessionTopic, serializeSessionMessage(message).bytes, deduplicationId, message.additionalHeaders())
val partyInfo = serviceHub.networkMapCache.getPartyInfo(party) ?: throw IllegalArgumentException("Don't know about $party") val partyInfo = serviceHub.networkMapCache.getPartyInfo(party) ?: throw IllegalArgumentException("Don't know about $party")
val address = serviceHub.networkService.getAddressOfParty(partyInfo) val address = serviceHub.networkService.getAddressOfParty(partyInfo)
val sequenceKey = when (message) { val sequenceKey = when (message) {
@ -60,6 +61,13 @@ class FlowMessagingImpl(val serviceHub: ServiceHubInternal): FlowMessaging {
serviceHub.networkService.send(networkMessage, address, sequenceKey = sequenceKey) serviceHub.networkService.send(networkMessage, address, sequenceKey = sequenceKey)
} }
private fun SessionMessage.additionalHeaders(): Map<String, String> {
return when (this) {
is InitialSessionMessage -> mapOf(P2PMessagingHeaders.Type.KEY to P2PMessagingHeaders.Type.SESSION_INIT_VALUE)
else -> emptyMap()
}
}
private fun serializeSessionMessage(message: SessionMessage): SerializedBytes<SessionMessage> { private fun serializeSessionMessage(message: SessionMessage): SerializedBytes<SessionMessage> {
return try { return try {
message.serialize() message.serialize()

View File

@ -5,5 +5,6 @@
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd"> xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog-ext http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-ext.xsd http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<include file="migration/node-info.changelog-init.xml"/> <include file="migration/node-info.changelog-init.xml"/>
<include file="migration/node-info.changelog-v1.xml"/>
</databaseChangeLog> </databaseChangeLog>

View File

@ -0,0 +1,14 @@
<?xml version="1.1" encoding="UTF-8" standalone="no"?>
<databaseChangeLog xmlns="http://www.liquibase.org/xml/ns/dbchangelog"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.5.xsd">
<changeSet author="R3.Corda" id="key/value node properties table">
<createTable tableName="node_properties">
<column name="property_key" type="NVARCHAR(255)">
<constraints nullable="false"/>
</column>
<column name="property_value" type="NVARCHAR(255)"/>
</createTable>
</changeSet>
</databaseChangeLog>

View File

@ -20,7 +20,6 @@ import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.BOB_NAME import net.corda.testing.core.BOB_NAME
import net.corda.testing.core.dummyCommand import net.corda.testing.core.dummyCommand
import net.corda.testing.core.singleIdentity import net.corda.testing.core.singleIdentity
import net.corda.testing.node.MockNetwork
import net.corda.testing.node.MockNodeParameters import net.corda.testing.node.MockNodeParameters
import net.corda.testing.node.internal.InternalMockNetwork import net.corda.testing.node.internal.InternalMockNetwork
import net.corda.testing.node.internal.startFlow import net.corda.testing.node.internal.startFlow

View File

@ -6,20 +6,29 @@ import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.crypto.generateKeyPair import net.corda.core.crypto.generateKeyPair
import net.corda.core.utilities.NetworkHostAndPort import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.internal.configureDatabase import net.corda.node.internal.configureDatabase
import net.corda.node.services.config.* import net.corda.node.services.config.CertChainPolicyConfig
import net.corda.node.services.config.EnterpriseConfiguration
import net.corda.node.services.config.MutualExclusionConfiguration
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.configureWithDevSSLCertificate
import net.corda.node.services.network.NetworkMapCacheImpl import net.corda.node.services.network.NetworkMapCacheImpl
import net.corda.node.services.network.PersistentNetworkMapCache import net.corda.node.services.network.PersistentNetworkMapCache
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor import net.corda.node.utilities.AffinityExecutor.ServiceAffinityExecutor
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.core.* import net.corda.testing.core.ALICE_NAME
import net.corda.testing.core.MAX_MESSAGE_SIZE
import net.corda.testing.core.SerializationEnvironmentRule
import net.corda.testing.core.freeLocalHostAndPort
import net.corda.testing.core.freePort
import net.corda.testing.internal.LogHelper import net.corda.testing.internal.LogHelper
import net.corda.testing.internal.rigorousMock import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO import net.corda.testing.node.MockServices.Companion.MOCK_VERSION_INFO
import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties import net.corda.testing.node.MockServices.Companion.makeTestDataSourceProperties
import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER import org.apache.activemq.artemis.api.core.Message.HDR_VALIDATED_USER
import org.apache.activemq.artemis.api.core.SimpleString import org.apache.activemq.artemis.api.core.SimpleString
import org.apache.activemq.artemis.api.core.client.ClientMessage
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy import org.assertj.core.api.Assertions.assertThatThrownBy
import org.junit.After import org.junit.After
@ -260,7 +269,6 @@ class ArtemisMessagingTest {
} }
} }
private fun startNodeMessagingClient() { private fun startNodeMessagingClient() {
messagingClient!!.start() messagingClient!!.start()
} }
@ -296,7 +304,9 @@ class ArtemisMessagingTest {
database, database,
networkMapCache, networkMapCache,
MetricRegistry(), MetricRegistry(),
maxMessageSize = maxMessageSize).apply { maxMessageSize = maxMessageSize,
isDrainingModeOn = { false },
drainingModeWasChangedEvents = PublishSubject.create()).apply {
config.configureWithDevSSLCertificate() config.configureWithDevSSLCertificate()
messagingClient = this messagingClient = this
} }

View File

@ -1,6 +1,5 @@
package net.corda.testing.node package net.corda.testing.node
import net.corda.core.CordaInternal
import net.corda.core.DoNotImplement import net.corda.core.DoNotImplement
import net.corda.core.crypto.CompositeKey import net.corda.core.crypto.CompositeKey
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
@ -19,7 +18,12 @@ import net.corda.core.utilities.ByteSequence
import net.corda.core.utilities.OpaqueBytes import net.corda.core.utilities.OpaqueBytes
import net.corda.core.utilities.getOrThrow import net.corda.core.utilities.getOrThrow
import net.corda.core.utilities.trace import net.corda.core.utilities.trace
import net.corda.node.services.messaging.* import net.corda.node.services.messaging.AcknowledgeHandle
import net.corda.node.services.messaging.Message
import net.corda.node.services.messaging.MessageHandler
import net.corda.node.services.messaging.MessageHandlerRegistration
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.messaging.ReceivedMessage
import net.corda.node.services.statemachine.DeduplicationId import net.corda.node.services.statemachine.DeduplicationId
import net.corda.node.utilities.AffinityExecutor import net.corda.node.utilities.AffinityExecutor
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
@ -260,6 +264,9 @@ class InMemoryMessagingNetwork private constructor(
override val uniqueMessageId: DeduplicationId, override val uniqueMessageId: DeduplicationId,
override val debugTimestamp: Instant = Instant.now(), override val debugTimestamp: Instant = Instant.now(),
override val senderUUID: String? = null) : Message { override val senderUUID: String? = null) : Message {
override val additionalHeaders: Map<String, String> = emptyMap()
override fun toString() = "$topic#${String(data.bytes)}" override fun toString() = "$topic#${String(data.bytes)}"
} }
@ -270,7 +277,10 @@ class InMemoryMessagingNetwork private constructor(
override val debugTimestamp: Instant, override val debugTimestamp: Instant,
override val peer: CordaX500Name, override val peer: CordaX500Name,
override val senderUUID: String? = null, override val senderUUID: String? = null,
override val senderSeqNo: Long? = null) : ReceivedMessage override val senderSeqNo: Long? = null) : ReceivedMessage {
override val additionalHeaders: Map<String, String> = emptyMap()
}
/** /**
* A [TestMessagingService] that provides a [MessagingService] abstraction that also contains the ability to * A [TestMessagingService] that provides a [MessagingService] abstraction that also contains the ability to
@ -368,7 +378,7 @@ class InMemoryMessagingNetwork private constructor(
override fun cancelRedelivery(retryId: Long) {} override fun cancelRedelivery(retryId: Long) {}
/** Returns the given (topic & session, data) pair as a newly created message object. */ /** Returns the given (topic & session, data) pair as a newly created message object. */
override fun createMessage(topic: String, data: ByteArray, deduplicationId: DeduplicationId): Message { override fun createMessage(topic: String, data: ByteArray, deduplicationId: DeduplicationId, additionalHeaders: Map<String, String>): Message {
return InMemoryMessage(topic, OpaqueBytes(data), deduplicationId) return InMemoryMessage(topic, OpaqueBytes(data), deduplicationId)
} }