From d4b6e32682db79b94af6e8da97d93d74fb1f85bd Mon Sep 17 00:00:00 2001 From: Clinton Alexander Date: Thu, 12 Jan 2017 14:35:40 +0000 Subject: [PATCH] t # This is a combination of 5 commits. Driver now queries webserver to ensure it has started. --- node/build.gradle | 3 ++ .../kotlin/net/corda/node/driver/Driver.kt | 43 ++++++++++++--- .../net/corda/node/utilities/JsonSupport.kt | 52 +++++++++++++------ .../kotlin/net/corda/node/webserver/Test.kt | 3 +- .../net/corda/node/webserver/WebServer.kt | 14 +++-- .../servlets/AttachmentDownloadServlet.kt | 2 +- .../servlets/DataUploadServlet.kt | 2 +- .../servlets/ObjectMapperConfig.kt} | 7 +-- .../servlets/ResponseFilter.kt | 2 +- .../kotlin/net/corda/node/JsonSupportTest.kt | 2 +- .../net/corda/bank/BankOfCordaHttpAPITest.kt | 2 +- .../kotlin/net/corda/irs/IRSDemoTest.kt | 14 +++-- .../net/corda/simulation/IRSSimulation.kt | 4 +- 13 files changed, 109 insertions(+), 41 deletions(-) rename node/src/main/kotlin/net/corda/node/{ => webserver}/servlets/AttachmentDownloadServlet.kt (98%) rename node/src/main/kotlin/net/corda/node/{ => webserver}/servlets/DataUploadServlet.kt (98%) rename node/src/main/kotlin/net/corda/node/{servlets/Config.kt => webserver/servlets/ObjectMapperConfig.kt} (67%) rename node/src/main/kotlin/net/corda/node/{ => webserver}/servlets/ResponseFilter.kt (96%) diff --git a/node/build.gradle b/node/build.gradle index b917a63935..991db103da 100644 --- a/node/build.gradle +++ b/node/build.gradle @@ -151,6 +151,9 @@ dependencies { compile 'io.atomix.copycat:copycat-server:1.1.4' compile 'io.atomix.catalyst:catalyst-netty:1.1.1' + // OkHTTP: Simple HTTP library. + compile "com.squareup.okhttp3:okhttp:$okhttp_version" + // Integration test helpers integrationTestCompile "junit:junit:$junit_version" diff --git a/node/src/main/kotlin/net/corda/node/driver/Driver.kt b/node/src/main/kotlin/net/corda/node/driver/Driver.kt index f8f9b591c8..3f99f81464 100644 --- a/node/src/main/kotlin/net/corda/node/driver/Driver.kt +++ b/node/src/main/kotlin/net/corda/node/driver/Driver.kt @@ -2,6 +2,8 @@ package net.corda.node.driver +import com.fasterxml.jackson.databind.ObjectMapper +import com.fasterxml.jackson.module.kotlin.KotlinModule import com.google.common.net.HostAndPort import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture @@ -13,6 +15,7 @@ import net.corda.core.crypto.Party import net.corda.core.node.NodeInfo import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceType +import net.corda.core.utilities.ApiUtils import net.corda.core.utilities.loggerFor import net.corda.node.services.User import net.corda.node.services.config.ConfigHelper @@ -23,7 +26,9 @@ import net.corda.node.services.messaging.CordaRPCClient import net.corda.node.services.messaging.NodeMessagingClient import net.corda.node.services.network.NetworkMapService import net.corda.node.services.transactions.RaftValidatingNotaryService +import net.corda.node.utilities.JsonSupport import net.corda.node.utilities.ServiceIdentityGenerator +import okhttp3.OkHttpClient import org.slf4j.Logger import java.io.File import java.net.* @@ -35,13 +40,14 @@ import java.time.ZoneOffset.UTC import java.time.format.DateTimeFormatter import java.time.temporal.ChronoUnit import java.util.* -import java.util.concurrent.Executors -import java.util.concurrent.Future -import java.util.concurrent.ScheduledExecutorService +import java.util.concurrent.* import java.util.concurrent.TimeUnit.MILLISECONDS import java.util.concurrent.TimeUnit.SECONDS -import java.util.concurrent.TimeoutException import java.util.concurrent.atomic.AtomicInteger +import com.sun.corba.se.spi.presentation.rmi.StubAdapter.request +import org.bouncycastle.crypto.tls.ConnectionEnd.client +import okhttp3.Request + /** * This file defines a small "Driver" DSL for starting up nodes that is only intended for development, demos and tests. @@ -90,7 +96,7 @@ interface DriverDSLExposedInterface { * * @param handle The handle for the node that this webserver connects to via RPC. */ - fun startWebserver(handle: NodeHandle): Future + fun startWebserver(handle: NodeHandle): ListenableFuture fun waitForAllNodesToFinish() } @@ -334,7 +340,7 @@ open class DriverDSL( val rpcOps = client.proxy(timeout = Duration.of(15, ChronoUnit.SECONDS)) return rpcOps.nodeIdentity() } catch(e: Exception) { - log.error("Retrying query node info at $nodeAddress") + log.debug("Retrying query node info at $nodeAddress") retries++ } @@ -416,12 +422,33 @@ open class DriverDSL( } } - override fun startWebserver(handle: NodeHandle): Future { + private fun queryWebserver(configuration: FullNodeConfiguration): HostAndPort? { + val protocol = if(configuration.useHTTPS) { "https://" } else { "http://" } + val url = URL(protocol + configuration.webAddress.toString() + "/api/status") + val client = OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(60, TimeUnit.SECONDS).build() + val retries = 5 + + for(i in 0..retries) { + try { + val response = client.newCall(Request.Builder().url(url).build()).execute() + if (response.isSuccessful && (response.body().string() == "started")) { + return configuration.webAddress + } + } catch(e: ConnectException) { + log.debug("Retrying webserver info at ${ configuration.webAddress}") + } + } + + log.error("Could not query node info after $retries retries") + return null + } + + override fun startWebserver(handle: NodeHandle): ListenableFuture { val debugPort = if (isDebug) debugPortAllocation.nextPort() else null return future { registerProcess(DriverDSL.startWebserver(executorService, handle.configuration, debugPort)) - handle.configuration.webAddress + queryWebserver(handle.configuration)!! } } diff --git a/node/src/main/kotlin/net/corda/node/utilities/JsonSupport.kt b/node/src/main/kotlin/net/corda/node/utilities/JsonSupport.kt index 452a49f385..ccd435f9a2 100644 --- a/node/src/main/kotlin/net/corda/node/utilities/JsonSupport.kt +++ b/node/src/main/kotlin/net/corda/node/utilities/JsonSupport.kt @@ -11,11 +11,12 @@ import com.fasterxml.jackson.databind.module.SimpleModule import com.fasterxml.jackson.module.kotlin.KotlinModule import net.corda.core.contracts.BusinessCalendar import net.corda.core.crypto.* +import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.NodeInfo -import net.corda.core.node.services.IdentityService import net.corda.core.serialization.deserialize import net.corda.core.serialization.serialize import net.i2p.crypto.eddsa.EdDSAPublicKey +import net.corda.core.node.services.IdentityService import java.math.BigDecimal import java.time.LocalDate import java.time.LocalDateTime @@ -25,7 +26,21 @@ import java.time.LocalDateTime * the java.time API, some core types, and Kotlin data classes. */ object JsonSupport { - val javaTimeModule : Module by lazy { + interface PartyObjectMapper { + fun partyFromName(partyName: String): Party? + } + + class RpcObjectMapper(val rpc: CordaRPCOps) : PartyObjectMapper, ObjectMapper() { + override fun partyFromName(partyName: String): Party? = rpc.partyFromName(partyName) + } + class IdentityObjectMapper(val identityService: IdentityService) : PartyObjectMapper, ObjectMapper(){ + override fun partyFromName(partyName: String) = identityService.partyFromName(partyName) + } + class NoPartyObjectMapper: PartyObjectMapper, ObjectMapper() { + override fun partyFromName(partyName: String) = throw UnsupportedOperationException() + } + + val javaTimeModule: Module by lazy { SimpleModule("java.time").apply { addSerializer(LocalDate::class.java, ToStringSerializer) addDeserializer(LocalDate::class.java, LocalDateDeserializer) @@ -34,7 +49,7 @@ object JsonSupport { } } - val cordaModule : Module by lazy { + val cordaModule: Module by lazy { SimpleModule("core").apply { addSerializer(Party::class.java, PartySerializer) addDeserializer(Party::class.java, PartyDeserializer) @@ -61,18 +76,24 @@ object JsonSupport { } } - fun createDefaultMapper(identities: IdentityService): ObjectMapper = - ServiceHubObjectMapper(identities).apply { - enable(SerializationFeature.INDENT_OUTPUT) - enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) - enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS) + /* Mapper requiring RPC support to deserialise parties from names */ + fun createDefaultMapper(rpc: CordaRPCOps): ObjectMapper = configureMapper(RpcObjectMapper(rpc)) - registerModule(javaTimeModule) - registerModule(cordaModule) - registerModule(KotlinModule()) - } + /* For testing or situations where deserialising parties is not required */ + fun createNonRpcMapper(): ObjectMapper = configureMapper(NoPartyObjectMapper()) - class ServiceHubObjectMapper(val identities: IdentityService) : ObjectMapper() + /* For testing with an in memory identity service */ + fun createInMemoryMapper(identityService: IdentityService) = configureMapper(IdentityObjectMapper(identityService)) + + private fun configureMapper(mapper: ObjectMapper): ObjectMapper = mapper.apply { + enable(SerializationFeature.INDENT_OUTPUT) + enable(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY) + enable(DeserializationFeature.USE_BIG_DECIMAL_FOR_FLOATS) + + registerModule(javaTimeModule) + registerModule(cordaModule) + registerModule(KotlinModule()) + } object ToStringSerializer : JsonSerializer() { override fun serialize(obj: Any, generator: JsonGenerator, provider: SerializerProvider) { @@ -108,9 +129,10 @@ object JsonSupport { if (parser.currentToken == JsonToken.FIELD_NAME) { parser.nextToken() } - val mapper = parser.codec as ServiceHubObjectMapper + + val mapper = parser.codec as PartyObjectMapper // TODO this needs to use some industry identifier(s) not just these human readable names - return mapper.identities.partyFromName(parser.text) ?: throw JsonParseException(parser, "Could not find a Party with name: ${parser.text}") + return mapper.partyFromName(parser.text) ?: throw JsonParseException(parser, "Could not find a Party with name ${parser.text}") } } diff --git a/node/src/main/kotlin/net/corda/node/webserver/Test.kt b/node/src/main/kotlin/net/corda/node/webserver/Test.kt index 3ad504420b..7f01f53815 100644 --- a/node/src/main/kotlin/net/corda/node/webserver/Test.kt +++ b/node/src/main/kotlin/net/corda/node/webserver/Test.kt @@ -8,7 +8,8 @@ fun main(args: Array) { System.setProperty("consoleLogLevel", "info") driver { val node = startNode().get() - val server = WebServer(node.configuration).start() + //WebServer(node.configuration).start() // Old in memory way + startWebserver(node) waitForAllNodesToFinish() } } \ No newline at end of file diff --git a/node/src/main/kotlin/net/corda/node/webserver/WebServer.kt b/node/src/main/kotlin/net/corda/node/webserver/WebServer.kt index a3727150ef..1927d57555 100644 --- a/node/src/main/kotlin/net/corda/node/webserver/WebServer.kt +++ b/node/src/main/kotlin/net/corda/node/webserver/WebServer.kt @@ -6,9 +6,10 @@ import net.corda.core.utilities.loggerFor import net.corda.node.services.config.FullNodeConfiguration import net.corda.node.services.messaging.ArtemisMessagingComponent import net.corda.node.services.messaging.CordaRPCClient -import net.corda.node.servlets.AttachmentDownloadServlet -import net.corda.node.servlets.DataUploadServlet -import net.corda.node.servlets.ResponseFilter +import net.corda.node.webserver.servlets.AttachmentDownloadServlet +import net.corda.node.webserver.servlets.ObjectMapperConfig +import net.corda.node.webserver.servlets.DataUploadServlet +import net.corda.node.webserver.servlets.ResponseFilter import net.corda.node.webserver.internal.APIServerImpl import org.eclipse.jetty.server.* import org.eclipse.jetty.server.handler.HandlerCollection @@ -32,7 +33,10 @@ class WebServer(val config: FullNodeConfiguration) { val address = config.webAddress fun start() { - initWebServer(connectLocalRpcAsNodeUser()) + val server = initWebServer(connectLocalRpcAsNodeUser()) + while(server.isRunning) { + Thread.sleep(100) // TODO: Redesign + } } private fun initWebServer(localRpc: CordaRPCOps): Server { @@ -90,6 +94,7 @@ class WebServer(val config: FullNodeConfiguration) { server.handler = handlerCollection //runOnStop += Runnable { server.stop() } server.start() + println("Server started") log.info("Embedded web server is listening on", "http://${InetAddress.getLocalHost().hostAddress}:${connector.port}/") return server } @@ -101,6 +106,7 @@ class WebServer(val config: FullNodeConfiguration) { addServlet(AttachmentDownloadServlet::class.java, "/attachments/*") val resourceConfig = ResourceConfig() + resourceConfig.register(ObjectMapperConfig(localRpc)) resourceConfig.register(ResponseFilter()) resourceConfig.register(APIServerImpl(localRpc)) diff --git a/node/src/main/kotlin/net/corda/node/servlets/AttachmentDownloadServlet.kt b/node/src/main/kotlin/net/corda/node/webserver/servlets/AttachmentDownloadServlet.kt similarity index 98% rename from node/src/main/kotlin/net/corda/node/servlets/AttachmentDownloadServlet.kt rename to node/src/main/kotlin/net/corda/node/webserver/servlets/AttachmentDownloadServlet.kt index 52cd1e10c7..de40ec9bb4 100644 --- a/node/src/main/kotlin/net/corda/node/servlets/AttachmentDownloadServlet.kt +++ b/node/src/main/kotlin/net/corda/node/webserver/servlets/AttachmentDownloadServlet.kt @@ -1,4 +1,4 @@ -package net.corda.node.servlets +package net.corda.node.webserver.servlets import net.corda.core.crypto.SecureHash import net.corda.core.node.services.StorageService diff --git a/node/src/main/kotlin/net/corda/node/servlets/DataUploadServlet.kt b/node/src/main/kotlin/net/corda/node/webserver/servlets/DataUploadServlet.kt similarity index 98% rename from node/src/main/kotlin/net/corda/node/servlets/DataUploadServlet.kt rename to node/src/main/kotlin/net/corda/node/webserver/servlets/DataUploadServlet.kt index 59b9a02edd..6bb64f7da4 100644 --- a/node/src/main/kotlin/net/corda/node/servlets/DataUploadServlet.kt +++ b/node/src/main/kotlin/net/corda/node/webserver/servlets/DataUploadServlet.kt @@ -1,4 +1,4 @@ -package net.corda.node.servlets +package net.corda.node.webserver.servlets import net.corda.core.utilities.loggerFor import net.corda.node.internal.Node diff --git a/node/src/main/kotlin/net/corda/node/servlets/Config.kt b/node/src/main/kotlin/net/corda/node/webserver/servlets/ObjectMapperConfig.kt similarity index 67% rename from node/src/main/kotlin/net/corda/node/servlets/Config.kt rename to node/src/main/kotlin/net/corda/node/webserver/servlets/ObjectMapperConfig.kt index 69b59cae85..78dbd1b131 100644 --- a/node/src/main/kotlin/net/corda/node/servlets/Config.kt +++ b/node/src/main/kotlin/net/corda/node/webserver/servlets/ObjectMapperConfig.kt @@ -1,6 +1,7 @@ -package net.corda.node.servlets +package net.corda.node.webserver.servlets import com.fasterxml.jackson.databind.ObjectMapper +import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.ServiceHub import net.corda.node.utilities.JsonSupport import javax.ws.rs.ext.ContextResolver @@ -11,7 +12,7 @@ import javax.ws.rs.ext.Provider * and to organise serializers / deserializers for java.time.* classes as necessary. */ @Provider -class Config(val services: ServiceHub) : ContextResolver { - val defaultObjectMapper = JsonSupport.createDefaultMapper(services.identityService) +class ObjectMapperConfig(rpc: CordaRPCOps) : ContextResolver { + val defaultObjectMapper = JsonSupport.createDefaultMapper(rpc) override fun getContext(type: Class<*>) = defaultObjectMapper } diff --git a/node/src/main/kotlin/net/corda/node/servlets/ResponseFilter.kt b/node/src/main/kotlin/net/corda/node/webserver/servlets/ResponseFilter.kt similarity index 96% rename from node/src/main/kotlin/net/corda/node/servlets/ResponseFilter.kt rename to node/src/main/kotlin/net/corda/node/webserver/servlets/ResponseFilter.kt index e39eb97b95..6700b8bb0a 100644 --- a/node/src/main/kotlin/net/corda/node/servlets/ResponseFilter.kt +++ b/node/src/main/kotlin/net/corda/node/webserver/servlets/ResponseFilter.kt @@ -1,4 +1,4 @@ -package net.corda.node.servlets +package net.corda.node.webserver.servlets import javax.ws.rs.container.ContainerRequestContext import javax.ws.rs.container.ContainerResponseContext diff --git a/node/src/test/kotlin/net/corda/node/JsonSupportTest.kt b/node/src/test/kotlin/net/corda/node/JsonSupportTest.kt index bb9f5f66ab..7248f4fb7e 100644 --- a/node/src/test/kotlin/net/corda/node/JsonSupportTest.kt +++ b/node/src/test/kotlin/net/corda/node/JsonSupportTest.kt @@ -15,7 +15,7 @@ import kotlin.test.assertEquals class JsonSupportTest { companion object { - val mapper = JsonSupport.createDefaultMapper(MockIdentityService(mutableListOf())) + val mapper = JsonSupport.createNonRpcMapper() } @Property diff --git a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt index 6388d342da..f7f4fc1725 100644 --- a/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt +++ b/samples/bank-of-corda-demo/src/integration-test/kotlin/net/corda/bank/BankOfCordaHttpAPITest.kt @@ -18,7 +18,7 @@ class BankOfCordaHttpAPITest { startNode("BankOfCorda", setOf(ServiceInfo(SimpleNotaryService.type))), startNode("BigCorporation") ).getOrThrow() - val nodeBankOfCordaApiAddr = nodeBankOfCorda.configuration.webAddress + val nodeBankOfCordaApiAddr = startWebserver(nodeBankOfCorda).getOrThrow() assert(BankOfCordaClientApi(nodeBankOfCordaApiAddr).requestWebIssue(IssueRequestParams(1000, "USD", "BigCorporation", "1", "BankOfCorda"))) }, isDebug = true) } diff --git a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt index e505818d81..9376378a51 100644 --- a/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt +++ b/samples/irs-demo/src/integration-test/kotlin/net/corda/irs/IRSDemoTest.kt @@ -35,14 +35,20 @@ class IRSDemoTest : IntegrationTestCategory { startNode("Bank B") ).getOrThrow() + val (controllerAddr, nodeAAddr, nodeBAddr) = Futures.allAsList( + startWebserver(controller), + startWebserver(nodeA), + startWebserver(nodeB) + ).getOrThrow() + val nextFixingDates = getFixingDateObservable(nodeA.configuration) - runUploadRates(controller.configuration.webAddress) - runTrade(nodeA.configuration.webAddress) + runUploadRates(controllerAddr) + runTrade(nodeAAddr) // Wait until the initial trade and all scheduled fixings up to the current date have finished nextFixingDates.first { it == null || it > currentDate } - runDateChange(nodeB.configuration.webAddress) + runDateChange(nodeBAddr) nextFixingDates.first { it == null || it > futureDate } } } @@ -78,4 +84,4 @@ class IRSDemoTest : IntegrationTestCategory { val url = URL("http://$host/upload/interest-rates") assert(uploadFile(url, fileContents)) } -} \ No newline at end of file +} diff --git a/samples/irs-demo/src/main/kotlin/net/corda/simulation/IRSSimulation.kt b/samples/irs-demo/src/main/kotlin/net/corda/simulation/IRSSimulation.kt index 4b67d341eb..97c8f9d3ce 100644 --- a/samples/irs-demo/src/main/kotlin/net/corda/simulation/IRSSimulation.kt +++ b/samples/irs-demo/src/main/kotlin/net/corda/simulation/IRSSimulation.kt @@ -11,6 +11,7 @@ import net.corda.core.contracts.UniqueIdentifier import net.corda.core.flatMap import net.corda.core.flows.FlowStateMachine import net.corda.core.map +import net.corda.core.messaging.CordaRPCOps import net.corda.core.node.services.linearHeadsOfType import net.corda.core.success import net.corda.core.transactions.SignedTransaction @@ -18,6 +19,7 @@ import net.corda.flows.TwoPartyDealFlow.Acceptor import net.corda.flows.TwoPartyDealFlow.AutoOffer import net.corda.flows.TwoPartyDealFlow.Instigator import net.corda.irs.contract.InterestRateSwap +import net.corda.node.internal.CordaRPCOpsImpl import net.corda.node.utilities.databaseTransaction import net.corda.testing.initiateSingleShotFlow import net.corda.testing.node.InMemoryMessagingNetwork @@ -30,7 +32,7 @@ import java.util.* * A simulation in which banks execute interest rate swaps with each other, including the fixing events. */ class IRSSimulation(networkSendManuallyPumped: Boolean, runAsync: Boolean, latencyInjector: InMemoryMessagingNetwork.LatencyCalculator?) : Simulation(networkSendManuallyPumped, runAsync, latencyInjector) { - val om = net.corda.node.utilities.JsonSupport.createDefaultMapper(MockIdentityService(network.identities)) + val om = net.corda.node.utilities.JsonSupport.createInMemoryMapper(MockIdentityService(network.identities)) init { currentDateAndTime = LocalDate.of(2016, 3, 8).atStartOfDay()