CORDA-2647 - Prevent registration when previous state exists

This commit is contained in:
Dimos Raptis 2019-03-13 13:55:53 +00:00 committed by Mike Hearn
parent b0cf41ef58
commit 4e9d1f1924
2 changed files with 136 additions and 3 deletions

View File

@ -1,6 +1,7 @@
package net.corda.node.internal.subcommands
import net.corda.cliutils.CliWrapperBase
import net.corda.core.internal.VisibleForTesting
import net.corda.core.internal.createFile
import net.corda.core.internal.div
import net.corda.core.internal.exists
@ -12,10 +13,14 @@ import net.corda.node.internal.NodeStartupLogging.Companion.logger
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NodeRegistrationHelper
import net.corda.nodeapi.internal.persistence.NODE_DATABASE_PREFIX
import picocli.CommandLine.Mixin
import picocli.CommandLine.Option
import java.io.File
import java.lang.IllegalStateException
import java.nio.file.Path
import java.sql.DriverManager
import java.sql.SQLException
import java.util.function.Consumer
class InitialRegistrationCli(val startup: NodeStartup): CliWrapperBase("initial-registration", "Start initial node registration with Corda network to obtain certificate from the permissioning server.") {
@ -58,7 +63,12 @@ class InitialRegistration(val baseDirectory: Path, private val networkRootTrustS
private val nodeRegistration = NodeRegistrationOption(networkRootTrustStorePath, networkRootTrustStorePassword)
private fun registerWithNetwork(conf: NodeConfiguration) {
private val EXISTING_STATE_GENERIC_WARNING = "Please ensure there is no state from previous runs, before initiating registration of a node."
@VisibleForTesting
fun registerWithNetwork(conf: NodeConfiguration) {
verifyNoStateFromPreviousRuns(conf)
val versionInfo = startup.getVersionInfo()
println("\n" +
@ -82,10 +92,35 @@ class InitialRegistration(val baseDirectory: Path, private val networkRootTrustS
println("Corda node will now terminate.")
}
private fun verifyNoStateFromPreviousRuns(conf: NodeConfiguration) {
val artemisDirectory = baseDirectory / "artemis"
check(!artemisDirectory.exists()) { "The node folder contains an artemis directory. $EXISTING_STATE_GENERIC_WARNING" }
val brokersDirectory = baseDirectory / "brokers"
check(!brokersDirectory.exists()) { "There node folder contains a brokers directory. $EXISTING_STATE_GENERIC_WARNING" }
val datasourceProps = conf.dataSourceProperties
if (datasourceProps.isEmpty) throw IllegalStateException("There must be a database configured.")
val datasourceUrl = datasourceProps.getProperty("dataSource.url")
val datasourceUser = datasourceProps.getProperty("dataSource.user")
val datasourcePassword = datasourceProps.getProperty("dataSource.password")
try {
val connection = DriverManager.getConnection(datasourceUrl, datasourceUser, datasourcePassword)
val connectionMetadata = connection.metaData
// Accounting for different case-sensitivity behaviours (i.e. H2 creates tables in upper-case in some cases)
val tablesLowerCaseResultSet = connectionMetadata.getTables(null, null, "$NODE_DATABASE_PREFIX%", null)
val tablesUpperCaseResultSet = connectionMetadata.getTables(null, null, "${NODE_DATABASE_PREFIX.toUpperCase()}%", null)
check(!tablesLowerCaseResultSet.first() && !tablesUpperCaseResultSet.first()) {
"The database contains Corda-specific tables, while it should be empty. $EXISTING_STATE_GENERIC_WARNING"
}
} catch (exception: SQLException) {
throw Exception("An error occurred whilst connecting to \"$datasourceUrl\". ", exception)
}
}
private fun initialRegistration(config: NodeConfiguration) {
// Null checks for [compatibilityZoneURL], [rootTruststorePath] and [rootTruststorePassword] has been done in [CmdLineOptions.loadConfig]
attempt { registerWithNetwork(config) }.doOnFailure(Consumer(this::handleRegistrationError)) as Try.Success
// At this point the node registration was successful. We can delete the marker file.
attempt { registerWithNetwork(config) }.doOnFailure(Consumer(this::handleRegistrationError))
deleteNodeRegistrationMarker(baseDirectory)
}

View File

@ -0,0 +1,98 @@
package net.corda.node.internal.subcommands
import net.corda.core.internal.deleteRecursively
import net.corda.core.internal.div
import net.corda.node.internal.NodeStartup
import net.corda.node.services.config.NodeConfiguration
import net.corda.node.internal.Node
import org.h2.tools.Server
import org.junit.AfterClass
import org.junit.BeforeClass
import org.junit.Test
import org.mockito.Mockito
import org.mockito.Mockito.mock
import java.lang.IllegalStateException
import java.nio.file.Files
import java.sql.DriverManager
import java.util.*
class InitialRegistrationCliTest {
companion object {
private lateinit var initialRegistration: InitialRegistration
private val networkTrustRootPassword = "some-password"
private val nodeStartup = mock(NodeStartup::class.java)
private val baseDirectory = Files.createTempDirectory("base-dir")!!
private val networkTrustRootFile = Files.createTempFile("trust-root-store", "jks")
private lateinit var nodeConfiguration: NodeConfiguration
private lateinit var datasourceProperties: Properties
private lateinit var node: Node
private val h2jdbcUrl = "jdbc:h2:tcp://localhost:10009/~/node"
private val h2User = "sa"
private val h2Password = ""
private lateinit var server: Server
@BeforeClass
@JvmStatic
fun prepare() {
nodeConfiguration = mock(NodeConfiguration::class.java)
datasourceProperties = Properties()
node = mock(Node::class.java)
Mockito.`when`(node.configuration).thenReturn(nodeConfiguration)
Mockito.`when`(nodeConfiguration.dataSourceProperties).thenReturn(datasourceProperties)
server = Server.createTcpServer("-tcpPort", "10009", "-tcpAllowOthers", "-tcpDaemon").start()
executeSqlStatement("CREATE TABLE NODE_ATTACHMENTS(USERNAME VARCHAR(20));")
initialRegistration = InitialRegistration(baseDirectory, networkTrustRootFile, networkTrustRootPassword, nodeStartup)
}
@AfterClass
@JvmStatic
fun cleanup() {
baseDirectory.deleteRecursively()
networkTrustRootFile.deleteRecursively()
executeSqlStatement("DROP TABLE NODE_ATTACHMENTS;")
server.shutdown()
}
private fun executeSqlStatement(sqlStatement: String) {
val connection = DriverManager.getConnection(h2jdbcUrl, h2User, h2Password)
val statement = connection.createStatement()
statement.execute(sqlStatement)
}
}
@Test(expected = IllegalStateException::class)
fun `registration fails when there is existing artemis folder`() {
Files.createDirectories(baseDirectory / "artemis")
initialRegistration.registerWithNetwork(node.configuration)
}
@Test(expected = IllegalStateException::class)
fun `registration fails when there is existing brokers folder`() {
Files.createDirectories(baseDirectory / "brokers")
initialRegistration.registerWithNetwork(node.configuration)
}
@Test(expected = IllegalStateException::class)
fun `registration fails when database contains tables`() {
datasourceProperties.setProperty("dataSource.url", h2jdbcUrl)
datasourceProperties.setProperty("dataSource.user", h2User)
datasourceProperties.setProperty("dataSource.password", h2Password)
Mockito.`when`(nodeConfiguration.dataSourceProperties).thenReturn(datasourceProperties)
initialRegistration.registerWithNetwork(node.configuration)
}
}