INFRA-545: Convert a few tests to unit tests; rename another test (#6562)

This commit is contained in:
Yiftach Kaplan 2020-08-10 15:25:28 +01:00 committed by GitHub
parent 66406ba0fb
commit 8aafb1db4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 262 additions and 153 deletions

View File

@ -8,7 +8,6 @@ import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.node.flows.isQuasarAgentSpecified
import net.corda.node.services.Permissions
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.incrementalPortAllocation
@ -16,7 +15,6 @@ import net.corda.testing.node.User
import net.corda.testing.node.internal.enclosedCordapp
import org.h2.jdbc.JdbcSQLNonTransientException
import org.junit.Test
import java.net.InetAddress
import java.sql.DriverManager
import kotlin.test.assertFailsWith
import kotlin.test.assertNull
@ -46,88 +44,6 @@ class H2SecurityTests {
}
}
@Test(timeout=300_000)
fun `h2 server on the host name requires non-default database password`() {
driver(DriverParameters(
inMemoryDB = false,
startNodesInProcess = isQuasarAgentSpecified(),
notarySpecs = emptyList(),
cordappsForAllNodes = emptyList()
)) {
assertFailsWith(CouldNotCreateDataSourceException::class) {
startNode(customOverrides = mapOf(h2AddressKey to "${InetAddress.getLocalHost().hostName}:${getFreePort()}")).getOrThrow()
}
}
}
@Test(timeout=300_000)
fun `h2 server on the external host IP requires non-default database password`() {
driver(DriverParameters(
inMemoryDB = false,
startNodesInProcess = isQuasarAgentSpecified(),
notarySpecs = emptyList(),
cordappsForAllNodes = emptyList()
)) {
assertFailsWith(CouldNotCreateDataSourceException::class) {
startNode(customOverrides = mapOf(h2AddressKey to "${InetAddress.getLocalHost().hostAddress}:${getFreePort()}")).getOrThrow()
}
}
}
@Test(timeout=300_000)
fun `h2 server on host name requires non-blank database password`() {
driver(DriverParameters(
inMemoryDB = false,
startNodesInProcess = isQuasarAgentSpecified(),
notarySpecs = emptyList(),
cordappsForAllNodes = emptyList()
)) {
assertFailsWith(CouldNotCreateDataSourceException::class) {
startNode(customOverrides = mapOf(h2AddressKey to "${InetAddress.getLocalHost().hostName}:${getFreePort()}",
dbPasswordKey to " ")).getOrThrow()
}
}
}
@Test(timeout=300_000)
fun `h2 server on external host IP requires non-blank database password`() {
driver(DriverParameters(
inMemoryDB = false,
startNodesInProcess = isQuasarAgentSpecified(),
notarySpecs = emptyList(),
cordappsForAllNodes = emptyList()
)) {
assertFailsWith(CouldNotCreateDataSourceException::class) {
startNode(customOverrides = mapOf(h2AddressKey to "${InetAddress.getLocalHost().hostAddress}:${getFreePort()}",
dbPasswordKey to " ")).getOrThrow()
}
}
}
@Test(timeout=300_000)
fun `h2 server on localhost runs with the default database password`() {
driver(DriverParameters(
inMemoryDB = false,
startNodesInProcess = false,
notarySpecs = emptyList(),
cordappsForAllNodes = emptyList()
)) {
startNode(customOverrides = mapOf(h2AddressKey to "localhost:${getFreePort()}")).getOrThrow()
}
}
@Test(timeout=300_000)
fun `h2 server to loopback IP runs with the default database password`() {
driver(DriverParameters(
inMemoryDB = false,
startNodesInProcess = isQuasarAgentSpecified(),
notarySpecs = emptyList(),
cordappsForAllNodes = emptyList()
)) {
startNode(customOverrides = mapOf(h2AddressKey to "127.0.0.1:${getFreePort()}")).getOrThrow()
}
}
@Test(timeout=300_000)
fun `remote code execution via h2 server is disabled`() {
driver(DriverParameters(

View File

@ -1,32 +0,0 @@
package net.corda.node.services.config
import net.corda.core.utilities.getOrThrow
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.incrementalPortAllocation
import net.corda.testing.driver.logFile
import org.junit.Assert.assertTrue
import org.junit.Test
class NodeConfigParsingTests {
@Test(timeout = 300_000)
fun `bad keys are ignored and warned for`() {
val portAllocator = incrementalPortAllocation()
driver(DriverParameters(
environmentVariables = mapOf(
"corda_bad_key" to "2077"),
startNodesInProcess = false,
portAllocation = portAllocator,
notarySpecs = emptyList())) {
val hasWarning = startNode()
.getOrThrow()
.logFile()
.readLines()
.any {
it.contains("(property or environment variable) cannot be mapped to an existing Corda")
}
assertTrue(hasWarning)
}
}
}

View File

@ -300,19 +300,8 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
val nodeProperties = NodePropertiesPersistentStore(StubbedNodeUniqueIdProvider::value, database, cacheFactory)
val flowLogicRefFactory = makeFlowLogicRefFactoryImpl()
// TODO Cancelling parameters updates - if we do that, how we ensure that no one uses cancelled parameters in the transactions?
val networkMapUpdater = NetworkMapUpdater(
networkMapCache,
NodeInfoWatcher(
configuration.baseDirectory,
@Suppress("LeakingThis")
rxIoScheduler,
Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)
),
networkMapClient,
configuration.baseDirectory,
configuration.extraNetworkMapKeys,
networkParametersStorage
).closeOnStop()
val networkMapUpdater = makeNetworkMapUpdater()
@Suppress("LeakingThis")
val transactionVerifierService = InMemoryTransactionVerifierService(
numberOfWorkers = transactionVerifierWorkerCount,
@ -347,16 +336,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
@Suppress("LeakingThis")
val smm = makeStateMachineManager()
val flowStarter = FlowStarterImpl(smm, flowLogicRefFactory, DBCheckpointStorage.MAX_CLIENT_ID_LENGTH)
private val schedulerService = NodeSchedulerService(
platformClock,
database,
flowStarter,
servicesForResolution,
flowLogicRefFactory,
nodeProperties,
configuration.drainingModePollPeriod,
unfinishedSchedules = busyNodeLatch
).tokenize().closeOnStop()
private val schedulerService = makeNodeSchedulerService()
private val cordappServices = MutableClassToInstanceMap.create<SerializeAsToken>()
private val shutdownExecutor = Executors.newSingleThreadExecutor()
@ -794,6 +774,31 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
// Extracted into a function to allow overriding in subclasses.
protected open fun makeFlowLogicRefFactoryImpl() = FlowLogicRefFactoryImpl(cordappLoader.appClassLoader)
protected open fun makeNetworkMapUpdater() = NetworkMapUpdater(
networkMapCache,
NodeInfoWatcher(
configuration.baseDirectory,
@Suppress("LeakingThis")
rxIoScheduler,
Duration.ofMillis(configuration.additionalNodeInfoPollingFrequencyMsec)
),
networkMapClient,
configuration.baseDirectory,
configuration.extraNetworkMapKeys,
networkParametersStorage
).closeOnStop()
protected open fun makeNodeSchedulerService() = NodeSchedulerService(
platformClock,
database,
flowStarter,
servicesForResolution,
flowLogicRefFactory,
nodeProperties,
configuration.drainingModePollPeriod,
unfinishedSchedules = busyNodeLatch
).tokenize().closeOnStop()
private fun makeCordappLoader(configuration: NodeConfiguration, versionInfo: VersionInfo): CordappLoader {
val generatedCordapps = mutableListOf(VirtualCordapp.generateCore(versionInfo))
notaryLoader?.builtInNotary?.let { notaryImpl ->
@ -838,7 +843,7 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
).tokenize()
}
private fun createExternalOperationExecutor(numberOfThreads: Int): ExecutorService {
protected open fun createExternalOperationExecutor(numberOfThreads: Int): ExecutorService {
when (numberOfThreads) {
1 -> log.info("Flow external operation executor has $numberOfThreads thread")
else -> log.info("Flow external operation executor has a max of $numberOfThreads threads")
@ -969,16 +974,18 @@ abstract class AbstractNode<S>(val configuration: NodeConfiguration,
protected open fun startDatabase() {
val props = configuration.dataSourceProperties
if (props.isEmpty) throw DatabaseConfigurationException("There must be a database configured.")
database.startHikariPool(props, metricRegistry) { dataSource, haveCheckpoints ->
SchemaMigration(dataSource, cordappLoader, configuration.baseDirectory, configuration.myLegalName)
.checkOrUpdate(schemaService.internalSchemas, runMigrationScripts, haveCheckpoints, true)
.checkOrUpdate(schemaService.appSchemas, runMigrationScripts, haveCheckpoints && !allowAppSchemaUpgradeWithCheckpoints, false)
}
startHikariPool()
// Now log the vendor string as this will also cause a connection to be tested eagerly.
logVendorString(database, log)
}
protected open fun startHikariPool() =
database.startHikariPool(configuration.dataSourceProperties, metricRegistry) { dataSource, haveCheckpoints ->
SchemaMigration(dataSource, cordappLoader, configuration.baseDirectory, configuration.myLegalName)
.checkOrUpdate(schemaService.internalSchemas, runMigrationScripts, haveCheckpoints, true)
.checkOrUpdate(schemaService.appSchemas, runMigrationScripts, haveCheckpoints && !allowAppSchemaUpgradeWithCheckpoints, false)
}
/** Loads and starts a notary service if it is configured. */
private fun maybeStartNotaryService(myNotaryIdentity: PartyAndCertificate?): NotaryService? {
return notaryLoader?.let { loader ->

View File

@ -526,12 +526,7 @@ open class Node(configuration: NodeConfiguration,
}
val databaseName = databaseUrl.removePrefix(h2Prefix).substringBefore(';')
val baseDir = Paths.get(databaseName).parent.toString()
val server = org.h2.tools.Server.createTcpServer(
"-tcpPort", effectiveH2Settings.address.port.toString(),
"-tcpAllowOthers",
"-tcpDaemon",
"-baseDir", baseDir,
"-key", "node", databaseName)
val server = createH2Server(baseDir, databaseName, effectiveH2Settings.address.port)
// override interface that createTcpServer listens on (which is always 0.0.0.0)
System.setProperty("h2.bindAddress", effectiveH2Settings.address.host)
runOnStop += server::stop
@ -553,6 +548,14 @@ open class Node(configuration: NodeConfiguration,
database.closeOnStop()
}
open fun createH2Server(baseDir: String, databaseName: String, port: Int): org.h2.tools.Server =
org.h2.tools.Server.createTcpServer(
"-tcpPort", port.toString(),
"-tcpAllowOthers",
"-tcpDaemon",
"-baseDir", baseDir,
"-key", "node", databaseName)
private val _startupComplete = openFuture<Unit>()
val startupComplete: CordaFuture<Unit> get() = _startupComplete

View File

@ -0,0 +1,173 @@
package net.corda.node.internal
import com.nhaarman.mockito_kotlin.atLeast
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.verify
import com.nhaarman.mockito_kotlin.whenever
import net.corda.core.serialization.SerializeAsToken
import net.corda.core.utilities.NetworkHostAndPort
import net.corda.node.VersionInfo
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.services.config.NodeH2Settings
import net.corda.node.services.events.NodeSchedulerService
import net.corda.node.services.messaging.MessagingService
import net.corda.node.services.network.NetworkMapUpdater
import net.corda.node.services.statemachine.StateMachineManager
import net.corda.nodeapi.internal.cryptoservice.CryptoService
import net.corda.nodeapi.internal.persistence.CouldNotCreateDataSourceException
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import org.assertj.core.api.Assertions.assertThat
import org.h2.tools.Server
import org.junit.Test
import java.net.InetAddress
import java.sql.Connection
import java.sql.DatabaseMetaData
import java.util.*
import java.util.concurrent.ExecutorService
import javax.sql.DataSource
import kotlin.test.assertFailsWith
class NodeH2SecurityTests {
@Test(timeout=300_000)
fun `h2 server on the host name requires non-default database password`() {
hikaryProperties.setProperty("dataSource.url", "jdbc:h2:file:my_file")
hikaryProperties.setProperty("dataSource.password", "")
address = NetworkHostAndPort(InetAddress.getLocalHost().hostName, 1080)
val node = MockNode()
val exception = assertFailsWith(CouldNotCreateDataSourceException::class) {
node.startDb()
}
assertThat(exception.message).contains("Database password is required for H2 server listening on ")
}
@Test(timeout=300_000)
fun `h2 server on the host IP requires non-default database password`() {
hikaryProperties.setProperty("dataSource.url", "jdbc:h2:file:my_file")
hikaryProperties.setProperty("dataSource.password", "")
address = NetworkHostAndPort(InetAddress.getLocalHost().hostAddress, 1080)
val node = MockNode()
val exception = assertFailsWith(CouldNotCreateDataSourceException::class) {
node.startDb()
}
assertThat(exception.message).contains("Database password is required for H2 server listening on")
}
@Test(timeout=300_000)
fun `h2 server on the host name requires non-blank database password`() {
hikaryProperties.setProperty("dataSource.url", "jdbc:h2:file:my_file")
hikaryProperties.setProperty("dataSource.password", " ")
address = NetworkHostAndPort(InetAddress.getLocalHost().hostName, 1080)
val node = MockNode()
val exception = assertFailsWith(CouldNotCreateDataSourceException::class) {
node.startDb()
}
assertThat(exception.message).contains("Database password is required for H2 server listening on")
}
@Test(timeout=300_000)
fun `h2 server on the host IP requires non-blank database password`() {
hikaryProperties.setProperty("dataSource.url", "jdbc:h2:file:my_file")
hikaryProperties.setProperty("dataSource.password", " ")
address = NetworkHostAndPort(InetAddress.getLocalHost().hostAddress, 1080)
val node = MockNode()
val exception = assertFailsWith(CouldNotCreateDataSourceException::class) {
node.startDb()
}
assertThat(exception.message).contains("Database password is required for H2 server listening on")
}
@Test(timeout=300_000)
fun `h2 server on localhost runs with the default database password`() {
hikaryProperties.setProperty("dataSource.url", "jdbc:h2:file:dir/file;")
hikaryProperties.setProperty("dataSource.password", "")
address = NetworkHostAndPort("localhost", 80)
val node = MockNode()
node.startDb()
verify(dataSource, atLeast(1)).connection
}
@Test(timeout=300_000)
fun `h2 server to loopback IP runs with the default database password`() {
hikaryProperties.setProperty("dataSource.url", "jdbc:h2:file:dir/file;")
hikaryProperties.setProperty("dataSource.password", "")
address = NetworkHostAndPort("127.0.0.1", 80)
val node = MockNode()
node.startDb()
verify(dataSource, atLeast(1)).connection
}
@Test(timeout=300_000)
fun `h2 server set allowedClasses system properties`() {
System.setProperty("h2.allowedClasses", "*")
hikaryProperties.setProperty("dataSource.url", "jdbc:h2:file:dir/file;")
hikaryProperties.setProperty("dataSource.password", "")
address = NetworkHostAndPort("127.0.0.1", 80)
val node = MockNode()
node.startDb()
val allowClasses = System.getProperty("h2.allowedClasses").split(",")
assertThat(allowClasses).contains("org.h2.mvstore.db.MVTableEngine",
"org.locationtech.jts.geom.Geometry" ,
"org.h2.server.TcpServer")
assertThat(allowClasses).doesNotContain("*")
}
private val config = mock<NodeConfiguration>()
private val hikaryProperties = Properties()
private val database = DatabaseConfig()
private var address: NetworkHostAndPort? = null
val dataSource = mock<DataSource>()
init {
whenever(config.database).thenReturn(database)
whenever(config.dataSourceProperties).thenReturn(hikaryProperties)
whenever(config.baseDirectory).thenReturn(mock())
whenever(config.effectiveH2Settings).thenAnswer { NodeH2Settings(address) }
}
private inner class MockNode: Node(config, VersionInfo.UNKNOWN, false) {
fun startDb() = startDatabase()
override fun makeMessagingService(): MessagingService {
val service = mock<MessagingService>(extraInterfaces = arrayOf(SerializeAsToken::class))
whenever(service.activeChange).thenReturn(mock())
return service
}
override fun makeStateMachineManager(): StateMachineManager = mock()
override fun createExternalOperationExecutor(numberOfThreads: Int): ExecutorService = mock()
override fun makeCryptoService(): CryptoService = mock()
override fun makeNetworkMapUpdater(): NetworkMapUpdater = mock()
override fun makeNodeSchedulerService(): NodeSchedulerService = mock()
override fun startHikariPool() {
val connection = mock<Connection>()
val metaData = mock<DatabaseMetaData>()
whenever(dataSource.connection).thenReturn(connection)
whenever(connection.metaData).thenReturn(metaData)
database.start(dataSource)
}
override fun createH2Server(baseDir: String, databaseName: String, port: Int): Server {
val server = mock<Server>()
whenever(server.start()).thenReturn(server)
whenever(server.url).thenReturn("")
return server
}
}
}

View File

@ -1,15 +1,30 @@
package net.corda.node.services.config
import com.natpryce.hamkrest.assertion.assertThat
import com.natpryce.hamkrest.containsSubstring
import com.nhaarman.mockito_kotlin.any
import com.nhaarman.mockito_kotlin.doReturn
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.whenever
import com.typesafe.config.Config
import com.typesafe.config.ConfigFactory
import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.delete
import net.corda.core.internal.div
import net.corda.core.utilities.getOrThrow
import net.corda.node.internal.Node
import org.apache.logging.log4j.LogManager
import org.apache.logging.log4j.core.Appender
import org.apache.logging.log4j.core.Logger
import org.apache.logging.log4j.core.LogEvent
import org.junit.After
import org.junit.Assert
import org.junit.Before
import org.junit.Test
import java.nio.file.Files
import java.nio.file.Path
import java.time.Duration
import kotlin.test.assertFalse
class ConfigHelperTests {
private var baseDir: Path? = null
@ -58,6 +73,33 @@ class ConfigHelperTests {
"corda.sshd.port" to sshPort.toString())
}
@Test(timeout = 300_000)
fun `bad keys are ignored and warned for`() {
val appender = mock<Appender>()
val logMessage = openFuture<String>()
whenever(appender.name).doReturn("mock")
whenever(appender.isStarted).doReturn(true)
whenever(appender.append(any())).thenAnswer {
val event: LogEvent = it.getArgument(0)
logMessage.set(event.message.format)
null
}
val logger = LogManager.getLogger(Node::class.java.canonicalName) as Logger
logger.addAppender(appender)
val baseDirectory = mock<Path>()
val configFile = createTempFile()
configFile.deleteOnExit()
System.setProperty("corda_bad_key", "2077")
val config = ConfigHelper.loadConfig(baseDirectory, configFile.toPath())
val warning = logMessage.getOrThrow(Duration.ofMinutes(3))
assertThat(warning, containsSubstring("(property or environment variable) cannot be mapped to an existing Corda"))
assertFalse(config.hasPath("corda_bad_key"))
System.clearProperty("corda_bad_key")
}
/**
* Load the node configuration with the given environment variable
* overrides.

View File

@ -29,7 +29,7 @@ import kotlin.test.fail
class SSHServerTest {
@Test(timeout=300_000)
fun `ssh server does not start be default`() {
fun `ssh server does not start by default`() {
val user = User("u", "p", setOf())
// The driver will automatically pick up the annotated flows below
driver(DriverParameters(notarySpecs = emptyList(), cordappsForAllNodes = emptyList())) {