From c27003b4716f5a6fafef26445f08edf247f4ee8e Mon Sep 17 00:00:00 2001 From: josecoll Date: Wed, 2 May 2018 15:51:45 +0100 Subject: [PATCH] Revert: Extensions to Corda Behave for CTS. --- .../qa/behave/tools/rpc-proxy/build.gradle | 85 +++++++++ .../service/proxy/RPCProxyWebService.kt | 163 ++++++++++++++++++ 2 files changed, 248 insertions(+) create mode 100644 testing/qa/behave/tools/rpc-proxy/build.gradle create mode 100644 testing/qa/behave/tools/rpc-proxy/src/main/kotlin/net/corda/behave/service/proxy/RPCProxyWebService.kt diff --git a/testing/qa/behave/tools/rpc-proxy/build.gradle b/testing/qa/behave/tools/rpc-proxy/build.gradle new file mode 100644 index 0000000000..6e4f9e54bd --- /dev/null +++ b/testing/qa/behave/tools/rpc-proxy/build.gradle @@ -0,0 +1,85 @@ +/* + * R3 Proprietary and Confidential + * + * Copyright (c) 2018 R3 Limited. All rights reserved. + * + * The intellectual and technical concepts contained herein are proprietary to R3 and its suppliers and are protected by trade secret law. + * + * Distribution of this file or any portion thereof via any medium without the express permission of R3 is strictly prohibited. + */ + +group 'net.corda.behave.tools' + +apply plugin: 'java' +apply plugin: 'kotlin' +apply plugin: 'maven-publish' + +configurations { + smokeTestCompile.extendsFrom testCompile + smokeTestRuntime.extendsFrom testRuntime +} + +sourceSets { + rpcProxy { + kotlin { + srcDir "src/main/kotlin" + } + resources { + srcDir "config/dev" + } + } + smokeTest { + kotlin { + compileClasspath += main.output + test.output + runtimeClasspath += main.output + test.output + srcDir file('src/smoke-test/kotlin') + } + } +} + +dependencies { + compile project(':test-utils') + compile "net.corda.plugins:cordform-common:$gradle_plugins_version" + + // Integration test helpers + testCompile "org.assertj:assertj-core:$assertj_version" + testCompile "junit:junit:$junit_version" + + compile project(':finance') + compileOnly project(':node-api') + compile project(':core') + compile project(':client:rpc') + + // includes jetty/jersey dependencies used by RPCProxyServer + compile project(':webserver') + + // Jetty http server + rpcProxyCompile "org.eclipse.jetty:jetty-servlet:$jetty_version" + rpcProxyCompile "org.eclipse.jetty:jetty-webapp:$jetty_version" + rpcProxyCompile "javax.servlet:javax.servlet-api:3.1.0" + + // Jersey for JAX-RS implementation for use in Jetty + rpcProxyCompile "org.glassfish.jersey.core:jersey-server:$jersey_version" + rpcProxyCompile "org.glassfish.jersey.containers:jersey-container-servlet-core:$jersey_version" + rpcProxyCompile "org.glassfish.jersey.containers:jersey-container-jetty-http:$jersey_version" + +} + +task smokeTest(type: Test) { + testClassesDirs = sourceSets.smokeTest.output.classesDirs + classpath = sourceSets.smokeTest.runtimeClasspath +} + +task rpcProxyJar(type: Jar) { + baseName "corda-rpcProxy" + from { + configurations.rpcProxyRuntime.collect { it.isDirectory() ? it : zipTree(it) } + } + with jar + exclude("META-INF/*.DSA") + exclude("META-INF/*.RSA") + exclude("META-INF/*.SF") + manifest { + attributes 'Main-Class': 'net.corda.behave.service.proxy.RPCProxyServerKt' + } +} diff --git a/testing/qa/behave/tools/rpc-proxy/src/main/kotlin/net/corda/behave/service/proxy/RPCProxyWebService.kt b/testing/qa/behave/tools/rpc-proxy/src/main/kotlin/net/corda/behave/service/proxy/RPCProxyWebService.kt new file mode 100644 index 0000000000..10ba274a76 --- /dev/null +++ b/testing/qa/behave/tools/rpc-proxy/src/main/kotlin/net/corda/behave/service/proxy/RPCProxyWebService.kt @@ -0,0 +1,163 @@ +package net.corda.behave.service.proxy + +import net.corda.behave.service.proxy.RPCProxyWebService.Companion.RPC_PROXY_PATH +import net.corda.client.rpc.CordaRPCClient +import net.corda.client.rpc.CordaRPCClientConfiguration +import net.corda.client.rpc.internal.KryoClientSerializationScheme +import net.corda.core.contracts.ContractState +import net.corda.core.flows.FlowLogic +import net.corda.core.messaging.CordaRPCOps +import net.corda.core.serialization.deserialize +import net.corda.core.serialization.internal.effectiveSerializationEnv +import net.corda.core.serialization.serialize +import net.corda.core.utilities.NetworkHostAndPort +import net.corda.core.utilities.contextLogger +import net.corda.core.utilities.getOrThrow +import net.corda.core.utilities.seconds +import java.io.InputStream +import javax.servlet.http.HttpServletRequest +import javax.ws.rs.Consumes +import javax.ws.rs.GET +import javax.ws.rs.POST +import javax.ws.rs.Path +import javax.ws.rs.core.Context +import javax.ws.rs.core.MediaType +import javax.ws.rs.core.Response +import javax.ws.rs.core.Response.status + +@Path(RPC_PROXY_PATH) +class RPCProxyWebService(targetHostAndPort: NetworkHostAndPort) { + + // see "NetworkInterface" port allocation definitions + private val targetPort = targetHostAndPort.port - 1000 + + init { + try { + effectiveSerializationEnv + } catch (e: IllegalStateException) { + try { + KryoClientSerializationScheme.initialiseSerialization() + } catch (e: IllegalStateException) { + // Race e.g. two of these constructed in parallel, ignore. + } + } + } + + companion object { + private val log = contextLogger() + const val DEFAULT_PASSWORD = "S0meS3cretW0rd" + const val RPC_PROXY_PATH = "rpc" + } + + @GET + @Path("my-ip") + fun myIp(@Context request: HttpServletRequest): Response { + return createResponse("HELLO! My ip is ${request.remoteHost}:${request.remotePort}") + } + + @GET + @Path("node-info") + fun nodeInfo(@Context request: HttpServletRequest): Response { + log.info("nodeInfo") + return use { + it.nodeInfo() + } + } + + @GET + @Path("registered-flows") + fun registeredFlows(@Context request: HttpServletRequest): Response { + log.info("registeredFlows") + return use { + it.registeredFlows() + } + } + + @GET + @Path("notary-identities") + fun notaryIdentities(@Context request: HttpServletRequest): Response { + log.info("networkMapSnapshot") + return use { + it.notaryIdentities() + } + } + + @GET + @Path("network-map-snapshot") + fun networkMapSnapshot(@Context request: HttpServletRequest): Response { + log.info("networkMapSnapshot") + return use { + it.networkMapSnapshot() + } + } + + @POST + @Path("parties-from-name") + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + fun partiesFromName(input: InputStream): Response { + log.info("partiesFromName") + val queryName = input.readBytes().deserialize() + return use { + it.partiesFromName(queryName, false) + } + } + + @POST + @Path("vault-query") + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + fun vaultQuery(input: InputStream): Response { + log.info("vaultQuery") + val contractStateType = input.readBytes().deserialize() + val clazz = Class.forName(contractStateType) as Class + return use { + log.info("Calling vaultQuery with: $clazz") + it.vaultQuery(clazz) + } + } + + @POST + @Path("start-flow") + @Consumes(MediaType.APPLICATION_OCTET_STREAM) + fun startFlow(input: InputStream): Response { + log.info("startFlow") + return use { rpcClient -> + val argsList = input.readBytes().deserialize>() + for (i in argsList.indices) { + log.info("$i: ${argsList[i]}") + } + val flowClass = Class.forName(argsList[0] as String) as Class> + val flowArgs = argsList.drop(1).toTypedArray() + log.info("Calling flow: $flowClass with arguments: ${flowArgs.asList()}") + rpcClient.startFlowDynamic(flowClass, *flowArgs).returnValue.getOrThrow() + } + } + + private fun use(action: (CordaRPCOps) -> T): Response { + val targetHost = NetworkHostAndPort("localhost", targetPort) + val config = object : CordaRPCClientConfiguration { + override val connectionMaxRetryInterval = 10.seconds + } + log.info("Establishing RPC connection to ${targetHost.host} on port ${targetHost.port} ...") + return try { + CordaRPCClient(targetHost, config).use("corda", DEFAULT_PASSWORD) { + log.info("RPC connection to ${targetHost.host}:${targetHost.port} established") + val client = it.proxy + val result = action(client) + log.info("CordaRPCOps result: $result") + return createResponse(result) + } + } catch (e: Exception) { + log.warn("RPC Proxy request failed: ", e) + e.printStackTrace() + status(Response.Status.INTERNAL_SERVER_ERROR).encoding(e.message) + }.build() + } + + private fun createResponse(payload: Any?): Response { + return if (payload != null) { + Response.ok(payload.serialize().bytes) + } else { + status(Response.Status.NOT_FOUND) + }.build() + } +}