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:
Viktor Kolomeyko
2019-09-26 16:01:14 +01:00
committed by Matthew Nesbit
parent 298d8ba69c
commit 51330c2e44
6 changed files with 215 additions and 37 deletions

View File

@ -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()
}
}
}

View File

@ -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)