Integration test for http network map service (#2078)

* make node info file copying optional by setting "compatabilityZoneURL" in driver
integration test for node using http network map using driver
some bug fixes

* rebase to feature branch and fixup

* add initialRegistration flag to driver

* remove useFileBaseNetworkMap flag, add network map server to DriverTest

* remove useFileBaseNetworkMap flag, add network map server to DriverTest

* use PortAllocation.Incremental instead of random

* * use PortAllocation.Incremental instead of random
* fix NodeInfoWatcher thread leak issue

* reset scheduler before create notary

* move port allocation out of companion object

* move port allocation out of companion object

* make node info file copier lateinit to avoid observable thread pool get created on init
This commit is contained in:
Patrick Kuo
2017-11-28 13:58:48 +00:00
committed by GitHub
parent 9fefabbb88
commit cc1fba641e
10 changed files with 337 additions and 165 deletions

View File

@ -0,0 +1,91 @@
package net.corda.node.services.network
import net.corda.core.node.NodeInfo
import net.corda.core.utilities.minutes
import net.corda.core.utilities.seconds
import net.corda.testing.ALICE
import net.corda.testing.BOB
import net.corda.testing.driver.NodeHandle
import net.corda.testing.driver.PortAllocation
import net.corda.testing.driver.driver
import net.corda.testing.node.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat
import org.junit.Test
import java.net.URL
class NetworkMapClientTest {
private val portAllocation = PortAllocation.Incremental(10000)
@Test
fun `nodes can see each other using the http network map`() {
NetworkMapServer(1.minutes, portAllocation.nextHostAndPort()).use {
val (host, port) = it.start()
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) {
val alice = startNode(providedName = ALICE.name)
val bob = startNode(providedName = BOB.name)
val notaryNode = defaultNotaryNode.get()
val aliceNode = alice.get()
val bobNode = bob.get()
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
}
}
}
@Test
fun `nodes process network map add updates correctly when adding new node to network map`() {
NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use {
val (host, port) = it.start()
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) {
val alice = startNode(providedName = ALICE.name)
val notaryNode = defaultNotaryNode.get()
val aliceNode = alice.get()
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo)
val bob = startNode(providedName = BOB.name)
val bobNode = bob.get()
// Wait for network map client to poll for the next update.
Thread.sleep(2.seconds.toMillis())
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
}
}
}
@Test
fun `nodes process network map remove updates correctly`() {
NetworkMapServer(1.seconds, portAllocation.nextHostAndPort()).use {
val (host, port) = it.start()
driver(portAllocation = portAllocation, compatibilityZoneURL = URL("http://$host:$port")) {
val alice = startNode(providedName = ALICE.name)
val bob = startNode(providedName = BOB.name)
val notaryNode = defaultNotaryNode.get()
val aliceNode = alice.get()
val bobNode = bob.get()
notaryNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
aliceNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
bobNode.onlySees(notaryNode.nodeInfo, aliceNode.nodeInfo, bobNode.nodeInfo)
it.removeNodeInfo(aliceNode.nodeInfo)
// Wait for network map client to poll for the next update.
Thread.sleep(2.seconds.toMillis())
notaryNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo)
bobNode.onlySees(notaryNode.nodeInfo, bobNode.nodeInfo)
}
}
}
private fun NodeHandle.onlySees(vararg nodes: NodeInfo) = assertThat(rpc.networkMapSnapshot()).containsOnly(*nodes)
}

View File

@ -184,11 +184,17 @@ open class Node(configuration: NodeConfiguration,
return if (!AddressUtils.isPublic(host)) {
val foundPublicIP = AddressUtils.tryDetectPublicIP()
if (foundPublicIP == null) {
val retrievedHostName = networkMapClient?.myPublicHostname()
if (retrievedHostName != null) {
log.info("Retrieved public IP from Network Map Service: $this. This will be used instead of the provided \"$host\" as the advertised address.")
try {
val retrievedHostName = networkMapClient?.myPublicHostname()
if (retrievedHostName != null) {
log.info("Retrieved public IP from Network Map Service: $this. This will be used instead of the provided \"$host\" as the advertised address.")
}
retrievedHostName
} catch (ignore: Throwable) {
// Cannot reach the network map service, ignore the exception and use provided P2P address instead.
log.warn("Cannot connect to the network map service for public IP detection.")
null
}
retrievedHostName
} else {
log.info("Detected public IP: ${foundPublicIP.hostAddress}. This will be used instead of the provided \"$host\" as the advertised address.")
foundPublicIP.hostAddress

View File

@ -101,7 +101,7 @@ open class PersistentNetworkMapCache(
select(get<String>(NodeInfoSchemaV1.PersistentNodeInfo::hash.name))
}
}
session.createQuery(query).resultList.map { SecureHash.sha256(it) }
session.createQuery(query).resultList.map { SecureHash.parse(it) }
}
}

View File

@ -1,86 +1,43 @@
package net.corda.node.services.network
import com.fasterxml.jackson.databind.ObjectMapper
import net.corda.core.crypto.*
import net.corda.core.identity.CordaX500Name
import net.corda.core.identity.PartyAndCertificate
import net.corda.core.node.NodeInfo
import net.corda.core.serialization.deserialize
import net.corda.core.crypto.sha256
import net.corda.core.serialization.serialize
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.core.utilities.seconds
import net.corda.node.services.network.TestNodeInfoFactory.createNodeInfo
import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.driver.PortAllocation
import net.corda.testing.node.network.NetworkMapServer
import org.assertj.core.api.Assertions.assertThat
import org.bouncycastle.asn1.x500.X500Name
import org.bouncycastle.cert.X509CertificateHolder
import org.eclipse.jetty.server.Server
import org.eclipse.jetty.server.ServerConnector
import org.eclipse.jetty.server.handler.HandlerCollection
import org.eclipse.jetty.servlet.ServletContextHandler
import org.eclipse.jetty.servlet.ServletHolder
import org.glassfish.jersey.server.ResourceConfig
import org.glassfish.jersey.servlet.ServletContainer
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.net.InetSocketAddress
import java.net.URL
import java.security.cert.CertPath
import java.security.cert.Certificate
import java.security.cert.CertificateFactory
import java.security.cert.X509Certificate
import javax.ws.rs.*
import javax.ws.rs.core.MediaType
import javax.ws.rs.core.Response
import javax.ws.rs.core.Response.ok
import kotlin.test.assertEquals
class NetworkMapClientTest {
@Rule
@JvmField
val testSerialization = SerializationEnvironmentRule(true)
private lateinit var server: Server
private lateinit var server: NetworkMapServer
private lateinit var networkMapClient: NetworkMapClient
private val rootCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val rootCACert = X509Utilities.createSelfSignedCACertificate(CordaX500Name(commonName = "Corda Node Root CA", organisation = "R3 LTD", locality = "London", country = "GB"), rootCAKey)
private val intermediateCAKey = Crypto.generateKeyPair(X509Utilities.DEFAULT_TLS_SIGNATURE_SCHEME)
private val intermediateCACert = X509Utilities.createCertificate(CertificateType.INTERMEDIATE_CA, rootCACert, rootCAKey, X500Name("CN=Corda Node Intermediate CA,L=London"), intermediateCAKey.public)
companion object {
private val cacheTimeout = 100000.seconds
}
@Before
fun setUp() {
server = Server(InetSocketAddress("localhost", 0)).apply {
handler = HandlerCollection().apply {
addHandler(ServletContextHandler().apply {
contextPath = "/"
val resourceConfig = ResourceConfig().apply {
// Add your API provider classes (annotated for JAX-RS) here
register(MockNetworkMapServer())
}
val jerseyServlet = ServletHolder(ServletContainer(resourceConfig)).apply { initOrder = 0 }// Initialise at server start
addServlet(jerseyServlet, "/*")
})
}
}
server.start()
while (!server.isStarted) {
Thread.sleep(100)
}
val hostAndPort = server.connectors.mapNotNull { it as? ServerConnector }.first()
networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.localPort}"))
server = NetworkMapServer(cacheTimeout, PortAllocation.Incremental(10000).nextHostAndPort())
val hostAndPort = server.start()
networkMapClient = NetworkMapClient(URL("http://${hostAndPort.host}:${hostAndPort.port}"))
}
@After
fun tearDown() {
server.stop()
server.close()
}
@Test
@ -102,7 +59,7 @@ class NetworkMapClientTest {
val nodeInfoHash2 = nodeInfo2.serialize().sha256()
assertThat(networkMapClient.getNetworkMap().networkMap).containsExactly(nodeInfoHash, nodeInfoHash2)
assertEquals(100000.seconds, networkMapClient.getNetworkMap().cacheMaxAge)
assertEquals(cacheTimeout, networkMapClient.getNetworkMap().cacheMaxAge)
assertEquals(nodeInfo2, networkMapClient.getNodeInfo(nodeInfoHash2))
}
@ -111,43 +68,3 @@ class NetworkMapClientTest {
assertEquals("test.host.name", networkMapClient.myPublicHostname())
}
}
@Path("network-map")
// This is a stub implementation of the network map rest API.
internal class MockNetworkMapServer {
val nodeInfoMap = mutableMapOf<SecureHash, NodeInfo>()
@POST
@Path("publish")
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
fun publishNodeInfo(input: InputStream): Response {
val registrationData = input.readBytes().deserialize<SignedData<NodeInfo>>()
val nodeInfo = registrationData.verified()
val nodeInfoHash = nodeInfo.serialize().sha256()
nodeInfoMap.put(nodeInfoHash, nodeInfo)
return ok().build()
}
@GET
@Produces(MediaType.APPLICATION_JSON)
fun getNetworkMap(): Response {
return Response.ok(ObjectMapper().writeValueAsString(nodeInfoMap.keys.map { it.toString() })).header("Cache-Control", "max-age=100000").build()
}
@GET
@Path("{var}")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
fun getNodeInfo(@PathParam("var") nodeInfoHash: String): Response {
val nodeInfo = nodeInfoMap[SecureHash.parse(nodeInfoHash)]
return if (nodeInfo != null) {
Response.ok(nodeInfo.serialize().bytes)
} else {
Response.status(Response.Status.NOT_FOUND)
}.build()
}
@GET
@Path("my-hostname")
fun getHostName(): Response {
return Response.ok("test.host.name").build()
}
}