Moved the webserver code into the main node module. Driver can now start webserver for nodes.

This commit is contained in:
Clinton Alexander 2016-12-23 15:49:15 +00:00 committed by Clinton Alexander
parent ecfb762143
commit 5f4d4c1da3
17 changed files with 145 additions and 102 deletions

View File

@ -1,5 +1,6 @@
package net.corda.node.driver
import com.google.common.net.HostAndPort
import net.corda.core.getOrThrow
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.ServiceInfo
@ -25,6 +26,14 @@ class DriverTests {
// Check that the port is bound
addressMustNotBeBound(executorService, hostAndPort)
}
fun webserverMustBeUp(webserverAddr: HostAndPort) {
addressMustBeBound(executorService, webserverAddr)
}
fun webserverMustBeDown(webserverAddr: HostAndPort) {
addressMustNotBeBound(executorService, webserverAddr)
}
}
@Test
@ -60,4 +69,15 @@ class DriverTests {
}
nodeMustBeDown(nodeInfo.nodeInfo)
}
@Test
fun `starting a node and independent web server works`() {
val addr = driver {
val node = startNode("test").getOrThrow()
val webserverAddr = startWebserver(node).getOrThrow()
webserverMustBeUp(webserverAddr)
webserverAddr
}
webserverMustBeDown(addr)
}
}

View File

@ -2,8 +2,6 @@
package net.corda.node.driver
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.module.SimpleModule
import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
@ -12,7 +10,6 @@ import com.typesafe.config.Config
import com.typesafe.config.ConfigRenderOptions
import net.corda.core.*
import net.corda.core.crypto.Party
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
@ -22,21 +19,21 @@ import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.config.NodeSSLConfiguration
import net.corda.node.services.messaging.ArtemisMessagingComponent
import net.corda.node.services.messaging.ArtemisMessagingServer
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 org.slf4j.Logger
import java.io.File
import java.net.*
import java.nio.file.Path
import java.nio.file.Paths
import java.time.Duration
import java.time.Instant
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
@ -88,6 +85,13 @@ interface DriverDSLExposedInterface {
type: ServiceType = RaftValidatingNotaryService.type,
rpcUsers: List<User> = emptyList()): Future<Pair<Party, List<NodeHandle>>>
/**
* Starts a web server for a node
*
* @param handle The handle for the node that this webserver connects to via RPC.
*/
fun startWebserver(handle: NodeHandle): Future<HostAndPort>
fun waitForAllNodesToFinish()
}
@ -322,16 +326,20 @@ open class DriverDSL(
executorService.shutdown()
}
private fun queryNodeInfo(webAddress: HostAndPort, sslConfig: NodeSSLConfiguration): NodeInfo? {
try {
val client = CordaRPCClient(webAddress, sslConfig)
private fun queryNodeInfo(nodeAddress: HostAndPort, sslConfig: NodeSSLConfiguration): NodeInfo? {
var retries = 0
while (retries < 5) try {
val client = CordaRPCClient(nodeAddress, sslConfig)
client.start(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER)
val rpcOps = client.proxy()
return rpcOps.nodeIdentity()
val rpcOps = client.proxy(timeout = Duration.of(15, ChronoUnit.SECONDS))
return rpcOps.nodeIdentity()
} catch(e: Exception) {
log.error("Could not query node info at $webAddress due to an exception.", e)
return null
log.error("Retrying query node info at $nodeAddress")
retries++
}
log.error("Could not query node info after $retries retries")
return null
}
override fun startNode(providedName: String?, advertisedServices: Set<ServiceInfo>,
@ -408,6 +416,15 @@ open class DriverDSL(
}
}
override fun startWebserver(handle: NodeHandle): Future<HostAndPort> {
val debugPort = if (isDebug) debugPortAllocation.nextPort() else null
return future {
registerProcess(DriverDSL.startWebserver(executorService, handle.configuration, debugPort))
handle.configuration.webAddress
}
}
override fun start() {
startNetworkMapService()
}
@ -480,11 +497,37 @@ open class DriverDSL(
builder.inheritIO()
builder.directory(nodeConf.baseDirectory.toFile())
val process = builder.start()
return Futures.allAsList(
addressMustBeBound(executorService, nodeConf.artemisAddress),
// TODO There is a race condition here. Even though the messaging address is bound it may be the case that
// the handlers for the advertised services are not yet registered. Needs rethinking.
).map { process }
// TODO There is a race condition here. Even though the messaging address is bound it may be the case that
// the handlers for the advertised services are not yet registered. Needs rethinking.
return addressMustBeBound(executorService, nodeConf.artemisAddress).map { process }
}
private fun startWebserver(
executorService: ScheduledExecutorService,
nodeConf: FullNodeConfiguration,
debugPort: Int?): ListenableFuture<Process> {
val className = "net.corda.node.webserver.MainKt" // cannot directly get class for this, so just use string
val separator = System.getProperty("file.separator")
val classpath = System.getProperty("java.class.path")
val path = System.getProperty("java.home") + separator + "bin" + separator + "java"
val debugPortArg = if (debugPort != null)
listOf("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$debugPort")
else
emptyList()
val javaArgs = listOf(path) +
listOf("-Dname=node-${nodeConf.artemisAddress}-webserver") + debugPortArg +
listOf(
"-cp", classpath, className,
"--base-directory", nodeConf.baseDirectory.toString(),
"--web-address", nodeConf.webAddress.toString())
val builder = ProcessBuilder(javaArgs)
builder.redirectError(Paths.get("error.$className.log").toFile())
builder.inheritIO()
builder.directory(nodeConf.baseDirectory.toFile())
val process = builder.start()
return addressMustBeBound(executorService, nodeConf.webAddress).map { process }
}
}
}

View File

@ -6,13 +6,12 @@ import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import net.corda.core.div
import net.corda.core.flatMap
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.getOrThrow
import net.corda.core.messaging.RPCOps
import net.corda.core.node.ServiceHub
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.node.services.UniquenessProvider
import net.corda.core.success
import net.corda.core.utilities.loggerFor
import net.corda.node.printBasicNodeInfo
import net.corda.node.serialization.NodeClock
@ -20,39 +19,19 @@ import net.corda.node.services.RPCUserService
import net.corda.node.services.RPCUserServiceImpl
import net.corda.node.services.api.MessagingServiceInternal
import net.corda.node.services.config.FullNodeConfiguration
import net.corda.node.services.messaging.ArtemisMessagingComponent.Companion.NODE_USER
import net.corda.node.services.messaging.ArtemisMessagingComponent.NetworkMapAddress
import net.corda.node.services.messaging.ArtemisMessagingServer
import net.corda.node.services.messaging.CordaRPCClient
import net.corda.node.services.messaging.NodeMessagingClient
import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.services.transactions.RaftUniquenessProvider
import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.node.servlets.AttachmentDownloadServlet
import net.corda.node.servlets.Config
import net.corda.node.servlets.DataUploadServlet
import net.corda.node.servlets.ResponseFilter
import net.corda.node.utilities.AffinityExecutor
import net.corda.node.utilities.databaseTransaction
import org.eclipse.jetty.server.*
import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.servlet.DefaultServlet
import org.eclipse.jetty.servlet.FilterHolder
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.servlet.ServletHolder
import org.eclipse.jetty.util.ssl.SslContextFactory
import org.eclipse.jetty.webapp.WebAppContext
import org.glassfish.jersey.server.ResourceConfig
import org.glassfish.jersey.server.ServerProperties
import org.glassfish.jersey.servlet.ServletContainer
import org.jetbrains.exposed.sql.Database
import java.io.RandomAccessFile
import java.lang.management.ManagementFactory
import java.lang.reflect.InvocationTargetException
import java.net.InetAddress
import java.nio.channels.FileLock
import java.time.Clock
import java.util.*
import javax.management.ObjectName
import javax.servlet.*
import kotlin.concurrent.thread
@ -293,6 +272,7 @@ class Node(override val configuration: FullNodeConfiguration,
chain.doFilter(request, response)
}
}
override fun init(filterConfig: FilterConfig?) {}
override fun destroy() {}
}

View File

@ -0,0 +1,9 @@
package net.corda.node.utilities
import com.google.common.net.HostAndPort
import com.typesafe.config.Config
import java.nio.file.Path
import java.nio.file.Paths
fun Config.getHostAndPort(name: String): HostAndPort = HostAndPort.fromString(getString(name))
fun Config.getPath(name: String): Path = Paths.get(getString(name))

View File

@ -0,0 +1,44 @@
package net.corda.node.webserver
import joptsimple.OptionParser
import net.corda.node.driver.driver
import net.corda.node.services.config.ConfigHelper
import net.corda.node.services.config.FullNodeConfiguration
import java.io.File
import java.nio.file.Paths
import kotlin.system.exitProcess
fun main(args: Array<String>) {
// TODO: Print basic webserver info
val parser = OptionParser()
val baseDirectoryArg = parser.accepts("base-directory", "The directory to put all files under").withRequiredArg()
val webAddressArg = parser.accepts("web-address", "The web address for this server to bind").withOptionalArg()
val logToConsoleArg = parser.accepts("log-to-console", "If set, prints logging to the console as well as to a file.")
val helpArg = parser.accepts("help").forHelp()
val cmdlineOptions = try {
parser.parse(*args)
} catch (ex: Exception) {
println("Unknown command line arguments: ${ex.message}")
exitProcess(1)
}
// Maybe render command line help.
if (cmdlineOptions.has(helpArg)) {
parser.printHelpOn(System.out)
exitProcess(0)
}
// Set up logging.
if (cmdlineOptions.has(logToConsoleArg)) {
// This property is referenced from the XML config file.
System.setProperty("consoleLogLevel", "info")
}
val baseDirectoryPath = Paths.get(cmdlineOptions.valueOf(baseDirectoryArg))
val config = ConfigHelper.loadConfig(baseDirectoryPath)
println("Starting server")
val nodeConf = FullNodeConfiguration(baseDirectoryPath, config)
val server = WebServer(nodeConf).start()
println("Exiting")
}

View File

@ -8,7 +8,7 @@ fun main(args: Array<String>) {
System.setProperty("consoleLogLevel", "info")
driver {
val node = startNode().get()
val server = WebServer(FullNodeConfiguration(node.config)).start()
val server = WebServer(node.configuration).start()
waitForAllNodesToFinish()
}
}

View File

@ -1,58 +0,0 @@
apply plugin: 'kotlin'
apply plugin: 'java'
apply plugin: 'net.corda.plugins.quasar-utils'
apply plugin: 'net.corda.plugins.publish-utils'
description 'Corda Webserver'
repositories {
mavenLocal()
mavenCentral()
maven {
url 'http://oss.sonatype.org/content/repositories/snapshots'
}
jcenter()
maven {
url 'https://dl.bintray.com/kotlin/exposed'
}
}
dependencies {
compile project(':core')
compile project(':node')
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
// Web stuff: for HTTP[S] servlets
compile "org.eclipse.jetty:jetty-servlet:${jetty_version}"
compile "org.eclipse.jetty:jetty-webapp:${jetty_version}"
compile "javax.servlet:javax.servlet-api:3.1.0"
compile "org.jolokia:jolokia-agent-war:2.0.0-M1"
compile "commons-fileupload:commons-fileupload:1.3.2"
// Log4J: logging framework (with SLF4J bindings)
compile "org.apache.logging.log4j:log4j-slf4j-impl:${log4j_version}"
compile "org.apache.logging.log4j:log4j-core:${log4j_version}"
compile "org.apache.logging.log4j:log4j-web:${log4j_version}"
// Jersey for JAX-RS implementation for use in Jetty
compile "org.glassfish.jersey.core:jersey-server:${jersey_version}"
compile "org.glassfish.jersey.containers:jersey-container-servlet-core:${jersey_version}"
compile "org.glassfish.jersey.containers:jersey-container-jetty-http:${jersey_version}"
// NOTE there is a Jackson version clash between jersey-media-json-jackson (v2.5.4) and jackson-module-kotlin (v.2.5.5)
// Have not found an Issue in the issue tracker for Jersey for this issue
compile ("org.glassfish.jersey.media:jersey-media-json-jackson:${jersey_version}") {
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-annotations'
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-databind'
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-core'
}
compile ("com.fasterxml.jackson.module:jackson-module-kotlin:${jackson_version}") {
exclude group: 'com.fasterxml.jackson.core', module: 'jackson-annotations'
}
compile "com.fasterxml.jackson.core:jackson-annotations:${jackson_version}"
}
task run(type: JavaExec) {
classpath = sourceSets.main.runtimeClasspath
main = 'net.corda.node.webserver.MainKt'
}

View File

@ -118,4 +118,4 @@ private fun sslConfigFor(nodename: String, certsPath: String?): SSLConfiguration
override val trustStorePassword: String = "trustpass"
override val certificatesDirectory: Path = if (certsPath != null) Paths.get(certsPath) else Paths.get("build") / "nodes" / nodename / "certificates"
}
}
}

View File

@ -7,6 +7,7 @@ import net.corda.core.getOrThrow
import net.corda.core.node.services.ServiceInfo
import net.corda.node.driver.driver
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.node.utilities.getHostAndPort
import org.junit.Test
class BankOfCordaHttpAPITest {
@ -21,4 +22,4 @@ class BankOfCordaHttpAPITest {
assert(BankOfCordaClientApi(nodeBankOfCordaApiAddr).requestWebIssue(IssueRequestParams(1000, "USD", "BigCorporation", "1", "BankOfCorda")))
}, isDebug = true)
}
}
}

View File

@ -14,6 +14,7 @@ import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.BOC_PARTY_REF
import net.corda.testing.expect
import net.corda.testing.expectEvents
import net.corda.node.utilities.getHostAndPort
import net.corda.testing.sequence
import org.junit.Test
import kotlin.test.assertTrue
@ -86,4 +87,4 @@ class BankOfCordaRPCClientTest {
}
}, isDebug = true)
}
}
}

View File

@ -8,6 +8,7 @@ import net.corda.node.driver.driver
import net.corda.node.services.User
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.node.utilities.getHostAndPort
import org.junit.Test
class TraderDemoTest {

View File

@ -6,7 +6,6 @@ include 'finance:isolated'
include 'core'
include 'node'
include 'node:capsule'
include 'node:webserver'
include 'client'
include 'experimental'
include 'experimental:sandbox'

View File

@ -4,6 +4,7 @@ package net.corda.testing
import com.google.common.net.HostAndPort
import com.google.common.util.concurrent.ListenableFuture
import com.typesafe.config.Config
import net.corda.core.contracts.StateRef
import net.corda.core.crypto.*
import net.corda.core.flows.FlowLogic
@ -155,3 +156,5 @@ data class TestNodeConfiguration(
override val emailAddress: String = "",
override val exportJMXto: String = "",
override val devMode: Boolean = true) : NodeConfiguration
fun Config.getHostAndPort(name: String) = HostAndPort.fromString(getString(name))