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 package net.corda.node.driver
import com.google.common.net.HostAndPort
import net.corda.core.getOrThrow import net.corda.core.getOrThrow
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
@ -25,6 +26,14 @@ class DriverTests {
// Check that the port is bound // Check that the port is bound
addressMustNotBeBound(executorService, hostAndPort) addressMustNotBeBound(executorService, hostAndPort)
} }
fun webserverMustBeUp(webserverAddr: HostAndPort) {
addressMustBeBound(executorService, webserverAddr)
}
fun webserverMustBeDown(webserverAddr: HostAndPort) {
addressMustNotBeBound(executorService, webserverAddr)
}
} }
@Test @Test
@ -60,4 +69,15 @@ class DriverTests {
} }
nodeMustBeDown(nodeInfo.nodeInfo) 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 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.net.HostAndPort
import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture import com.google.common.util.concurrent.ListenableFuture
@ -12,7 +10,6 @@ import com.typesafe.config.Config
import com.typesafe.config.ConfigRenderOptions import com.typesafe.config.ConfigRenderOptions
import net.corda.core.* import net.corda.core.*
import net.corda.core.crypto.Party import net.corda.core.crypto.Party
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.node.NodeInfo import net.corda.core.node.NodeInfo
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType 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.FullNodeConfiguration
import net.corda.node.services.config.NodeSSLConfiguration import net.corda.node.services.config.NodeSSLConfiguration
import net.corda.node.services.messaging.ArtemisMessagingComponent 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.CordaRPCClient
import net.corda.node.services.messaging.NodeMessagingClient import net.corda.node.services.messaging.NodeMessagingClient
import net.corda.node.services.network.NetworkMapService import net.corda.node.services.network.NetworkMapService
import net.corda.node.services.transactions.RaftValidatingNotaryService import net.corda.node.services.transactions.RaftValidatingNotaryService
import net.corda.node.utilities.JsonSupport
import net.corda.node.utilities.ServiceIdentityGenerator import net.corda.node.utilities.ServiceIdentityGenerator
import org.slf4j.Logger import org.slf4j.Logger
import java.io.File import java.io.File
import java.net.* import java.net.*
import java.nio.file.Path import java.nio.file.Path
import java.nio.file.Paths import java.nio.file.Paths
import java.time.Duration
import java.time.Instant import java.time.Instant
import java.time.ZoneOffset.UTC import java.time.ZoneOffset.UTC
import java.time.format.DateTimeFormatter import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit
import java.util.* import java.util.*
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.Future import java.util.concurrent.Future
@ -88,6 +85,13 @@ interface DriverDSLExposedInterface {
type: ServiceType = RaftValidatingNotaryService.type, type: ServiceType = RaftValidatingNotaryService.type,
rpcUsers: List<User> = emptyList()): Future<Pair<Party, List<NodeHandle>>> 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() fun waitForAllNodesToFinish()
} }
@ -322,16 +326,20 @@ open class DriverDSL(
executorService.shutdown() executorService.shutdown()
} }
private fun queryNodeInfo(webAddress: HostAndPort, sslConfig: NodeSSLConfiguration): NodeInfo? { private fun queryNodeInfo(nodeAddress: HostAndPort, sslConfig: NodeSSLConfiguration): NodeInfo? {
try { var retries = 0
val client = CordaRPCClient(webAddress, sslConfig) while (retries < 5) try {
val client = CordaRPCClient(nodeAddress, sslConfig)
client.start(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER) client.start(ArtemisMessagingComponent.NODE_USER, ArtemisMessagingComponent.NODE_USER)
val rpcOps = client.proxy() val rpcOps = client.proxy(timeout = Duration.of(15, ChronoUnit.SECONDS))
return rpcOps.nodeIdentity() return rpcOps.nodeIdentity()
} catch(e: Exception) { } catch(e: Exception) {
log.error("Could not query node info at $webAddress due to an exception.", e) log.error("Retrying query node info at $nodeAddress")
return null retries++
} }
log.error("Could not query node info after $retries retries")
return null
} }
override fun startNode(providedName: String?, advertisedServices: Set<ServiceInfo>, 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() { override fun start() {
startNetworkMapService() startNetworkMapService()
} }
@ -480,11 +497,37 @@ open class DriverDSL(
builder.inheritIO() builder.inheritIO()
builder.directory(nodeConf.baseDirectory.toFile()) builder.directory(nodeConf.baseDirectory.toFile())
val process = builder.start() 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 // 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. // the handlers for the advertised services are not yet registered. Needs rethinking.
).map { process } 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 com.google.common.util.concurrent.ListenableFuture
import net.corda.core.div import net.corda.core.div
import net.corda.core.flatMap 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.messaging.RPCOps
import net.corda.core.node.ServiceHub import net.corda.core.node.ServiceHub
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType import net.corda.core.node.services.ServiceType
import net.corda.core.node.services.UniquenessProvider import net.corda.core.node.services.UniquenessProvider
import net.corda.core.success
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.node.printBasicNodeInfo import net.corda.node.printBasicNodeInfo
import net.corda.node.serialization.NodeClock 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.RPCUserServiceImpl
import net.corda.node.services.api.MessagingServiceInternal import net.corda.node.services.api.MessagingServiceInternal
import net.corda.node.services.config.FullNodeConfiguration 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.ArtemisMessagingComponent.NetworkMapAddress
import net.corda.node.services.messaging.ArtemisMessagingServer 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.messaging.NodeMessagingClient
import net.corda.node.services.transactions.PersistentUniquenessProvider import net.corda.node.services.transactions.PersistentUniquenessProvider
import net.corda.node.services.transactions.RaftUniquenessProvider import net.corda.node.services.transactions.RaftUniquenessProvider
import net.corda.node.services.transactions.RaftValidatingNotaryService 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.AffinityExecutor
import net.corda.node.utilities.databaseTransaction 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 org.jetbrains.exposed.sql.Database
import java.io.RandomAccessFile import java.io.RandomAccessFile
import java.lang.management.ManagementFactory import java.lang.management.ManagementFactory
import java.lang.reflect.InvocationTargetException
import java.net.InetAddress
import java.nio.channels.FileLock import java.nio.channels.FileLock
import java.time.Clock import java.time.Clock
import java.util.*
import javax.management.ObjectName import javax.management.ObjectName
import javax.servlet.* import javax.servlet.*
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -293,6 +272,7 @@ class Node(override val configuration: FullNodeConfiguration,
chain.doFilter(request, response) chain.doFilter(request, response)
} }
} }
override fun init(filterConfig: FilterConfig?) {} override fun init(filterConfig: FilterConfig?) {}
override fun destroy() {} 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") System.setProperty("consoleLogLevel", "info")
driver { driver {
val node = startNode().get() val node = startNode().get()
val server = WebServer(FullNodeConfiguration(node.config)).start() val server = WebServer(node.configuration).start()
waitForAllNodesToFinish() 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

@ -7,6 +7,7 @@ import net.corda.core.getOrThrow
import net.corda.core.node.services.ServiceInfo import net.corda.core.node.services.ServiceInfo
import net.corda.node.driver.driver import net.corda.node.driver.driver
import net.corda.node.services.transactions.SimpleNotaryService import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.node.utilities.getHostAndPort
import org.junit.Test import org.junit.Test
class BankOfCordaHttpAPITest { class BankOfCordaHttpAPITest {

View File

@ -14,6 +14,7 @@ import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.testing.BOC_PARTY_REF import net.corda.testing.BOC_PARTY_REF
import net.corda.testing.expect import net.corda.testing.expect
import net.corda.testing.expectEvents import net.corda.testing.expectEvents
import net.corda.node.utilities.getHostAndPort
import net.corda.testing.sequence import net.corda.testing.sequence
import org.junit.Test import org.junit.Test
import kotlin.test.assertTrue import kotlin.test.assertTrue

View File

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

View File

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

View File

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