mirror of
https://github.com/corda/corda.git
synced 2025-06-18 15:18:16 +00:00
CORDA-3232: Support of multiple interfaces for RPC calls (#5495)
* CORDA-3232: Make backward compatible RPC client changes
Such that it will be able to talk to new and old server versions.
* CORDA-3232: Make backward compatible RPC server changes
Such that it will be able to talk to new and old client versions.
* CORDA-3232: Trick Detekt
* CORDA-3232: Integration test for multi-interface communication.
* CORDA-3232: Add legacy mode test.
* CORDA-3232: Making Detekt happier
* CORDA-3232: Fix Detekt baseline after merge with `4.3` branch
* CORDA-3232: Incrementing Platform version
As discussed with @lockathan
* CORDA-3232: Fix legacy test post platform version increment
* CORDA-3232: Use recursive logic to establish complete population of method names
* Revert "CORDA-3232: Incrementing Platform version"
This reverts commit d75f48aa
* CORDA-3232: Remove logic that conditions on PLATFORM_VERSION
* CORDA-3232: Making Detekt happier
* CORDA-3232: Few more changes after conversation with @mnesbit
* CORDA-3232: Make a strict match to `CordaRPCOps` on client side
Or else will fail:
net.corda.tools.shell.InteractiveShellIntegrationTest.dumpCheckpoints creates zip with json file for suspended flow
Flagging that `InternalCordaRPCOps.dumpCheckpoints` cannot be called.
* CORDA-3232: Address PR comments by @rick-r3
* CORDA-3232: Address further review input from @rick-r3
* Change the way how methods stored in the map;
* Extend test to make sure that `CordaRPCOps` can indeed be mixed with other RPC interfaces.
This commit is contained in:
committed by
Matthew Nesbit
parent
298d8ba69c
commit
51330c2e44
@ -0,0 +1,91 @@
|
||||
package net.corda.client.rpc
|
||||
|
||||
import com.nhaarman.mockito_kotlin.mock
|
||||
import net.corda.client.rpc.RPCMultipleInterfacesTests.StringRPCOpsImpl.testPhrase
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.testing.core.SerializationEnvironmentRule
|
||||
import net.corda.testing.node.internal.rpcDriver
|
||||
import net.corda.testing.node.internal.startRpcClient
|
||||
import org.assertj.core.api.Assertions
|
||||
import org.junit.Assert.*
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import rx.Observable
|
||||
|
||||
class RPCMultipleInterfacesTests {
|
||||
@Rule
|
||||
@JvmField
|
||||
val testSerialization = SerializationEnvironmentRule(true)
|
||||
|
||||
companion object {
|
||||
const val sampleSize = 30
|
||||
}
|
||||
|
||||
interface IntRPCOps : RPCOps {
|
||||
fun stream(size: Int): Observable<Int>
|
||||
|
||||
fun intTestMethod(): Int
|
||||
}
|
||||
|
||||
interface StringRPCOps : RPCOps {
|
||||
fun stream(size: Int): Observable<String>
|
||||
|
||||
fun stringTestMethod() : String
|
||||
}
|
||||
|
||||
private class IntRPCOpsImpl : IntRPCOps {
|
||||
override val protocolVersion = 1000
|
||||
|
||||
override fun stream(size: Int): Observable<Int> {
|
||||
return Observable.range(0, size)
|
||||
}
|
||||
|
||||
override fun intTestMethod(): Int = protocolVersion
|
||||
}
|
||||
|
||||
private object StringRPCOpsImpl : StringRPCOps {
|
||||
|
||||
const val testPhrase = "I work with Strings."
|
||||
|
||||
override val protocolVersion = 1000
|
||||
|
||||
override fun stream(size: Int): Observable<String> {
|
||||
return Observable.range(0, size).map { it.toString(8) }
|
||||
}
|
||||
|
||||
override fun stringTestMethod(): String = testPhrase
|
||||
}
|
||||
|
||||
private object MyCordaRpcOpsImpl : CordaRPCOps by mock() {
|
||||
override val protocolVersion = 1000
|
||||
}
|
||||
|
||||
interface ImaginaryFriend : RPCOps
|
||||
|
||||
@Test
|
||||
fun `can talk multiple interfaces`() {
|
||||
rpcDriver {
|
||||
val server = startRpcServer(listOps = listOf(IntRPCOpsImpl(), StringRPCOpsImpl, MyCordaRpcOpsImpl)).get()
|
||||
|
||||
val clientInt = startRpcClient<IntRPCOps>(server.broker.hostAndPort!!).get()
|
||||
val intList = clientInt.stream(sampleSize).toList().toBlocking().single()
|
||||
assertEquals(sampleSize, intList.size)
|
||||
|
||||
val clientString = startRpcClient<StringRPCOps>(server.broker.hostAndPort!!).get()
|
||||
val stringList = clientString.stream(sampleSize).toList().toBlocking().single()
|
||||
assertEquals(sampleSize, stringList.size)
|
||||
assertTrue(stringList.toString(), stringList.all { it.matches("[0-7]*".toRegex()) })
|
||||
assertEquals(testPhrase, clientString.stringTestMethod())
|
||||
|
||||
val rpcOpsClient = startRpcClient<CordaRPCOps>(server.broker.hostAndPort!!).get()
|
||||
assertFalse(rpcOpsClient.attachmentExists(SecureHash.zeroHash))
|
||||
|
||||
Assertions.assertThatThrownBy { startRpcClient<ImaginaryFriend>(server.broker.hostAndPort!!).get() }
|
||||
.hasCauseInstanceOf(RPCException::class.java).hasMessageContaining("possible client/server version skew")
|
||||
|
||||
server.rpcServer.close()
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,7 @@ import net.corda.core.context.Trace
|
||||
import net.corda.core.context.Trace.InvocationId
|
||||
import net.corda.core.identity.CordaX500Name
|
||||
import net.corda.core.internal.*
|
||||
import net.corda.core.messaging.CordaRPCOps
|
||||
import net.corda.core.messaging.RPCOps
|
||||
import net.corda.core.serialization.SerializationContext
|
||||
import net.corda.core.serialization.serialize
|
||||
@ -25,6 +26,7 @@ import net.corda.core.utilities.contextLogger
|
||||
import net.corda.core.utilities.debug
|
||||
import net.corda.core.utilities.getOrThrow
|
||||
import net.corda.nodeapi.RPCApi
|
||||
import net.corda.nodeapi.RPCApi.CLASS_METHOD_DIVIDER
|
||||
import net.corda.nodeapi.internal.DeduplicationChecker
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQException
|
||||
import org.apache.activemq.artemis.api.core.ActiveMQNotConnectedException
|
||||
@ -252,12 +254,13 @@ class RPCClientProxyHandler(
|
||||
throw RPCException("RPC server is not available.")
|
||||
|
||||
val replyId = InvocationId.newInstance()
|
||||
callSiteMap?.set(replyId, CallSite(method.name))
|
||||
val methodFqn = produceMethodFullyQualifiedName(method)
|
||||
callSiteMap?.set(replyId, CallSite(methodFqn))
|
||||
try {
|
||||
val serialisedArguments = (arguments?.toList() ?: emptyList()).serialize(context = serializationContextWithObservableContext)
|
||||
val request = RPCApi.ClientToServer.RpcRequest(
|
||||
clientAddress,
|
||||
method.name,
|
||||
methodFqn,
|
||||
serialisedArguments,
|
||||
replyId,
|
||||
sessionId,
|
||||
@ -282,6 +285,15 @@ class RPCClientProxyHandler(
|
||||
}
|
||||
}
|
||||
|
||||
private fun produceMethodFullyQualifiedName(method: Method) : String {
|
||||
// For CordaRPCOps send method only - for backwards compatibility
|
||||
return if (CordaRPCOps::class.java == rpcOpsClass) {
|
||||
method.name
|
||||
} else {
|
||||
rpcOpsClass.name + CLASS_METHOD_DIVIDER + method.name
|
||||
}
|
||||
}
|
||||
|
||||
private fun sendMessage(message: RPCApi.ClientToServer) {
|
||||
val artemisMessage = producerSession!!.createMessage(false)
|
||||
message.writeToClientMessage(artemisMessage)
|
||||
|
Reference in New Issue
Block a user