CORDA-1499, CORDA-1804 (#3607)

* Fix a typo in node_attchments_contracts table name. (#3202)

(cherry picked from commit 57d379597bcff397edd3b71ffb8ac9901be06b22)

* CORDA-1804 Corda node stops when detecting not migrated node_attachments_contracts table name (#3593)

Database table NODE_ATTACHMENTS_CONTRACT_CLASS_NAME in v3.0 was changed to NODE_ATTCHMENTS_CONTRACTS in v3.1 and then finally NODE_ATTACHMENTS_CONTRACTS on current master. Users may omit the upgrade note and run into errors. After the change the node will not start if the new table name is not found and any other older ones is found.

(cherry picked from commit 208ac49da0caaf0dfc5ade9f8bd8a49d13f17aae)

* Fixes after cherry-pick of 208ac49da0caaf0dfc5ade9f8bd8a49d13f17aae.
This commit is contained in:
szymonsztuka 2018-07-16 09:58:37 +01:00 committed by Katelyn Baker
parent 9259c9ec45
commit fad90eee8b
5 changed files with 109 additions and 0 deletions

View File

@ -66,6 +66,8 @@ Version 3.2
the same server. Current ``compatibilityZoneURL`` configurations remain valid. See both :doc:`corda-configuration-file` the same server. Current ``compatibilityZoneURL`` configurations remain valid. See both :doc:`corda-configuration-file`
and :doc:`permissioning` for details. and :doc:`permissioning` for details.
* Table name with a typo changed from ``NODE_ATTCHMENTS_CONTRACTS`` to ``NODE_ATTACHMENTS_CONTRACTS``.
.. _changelog_v3.1: .. _changelog_v3.1:
Version 3.1 Version 3.1

View File

@ -82,6 +82,8 @@ class CordaPersistence(
// Check not in read-only mode. // Check not in read-only mode.
transaction { transaction {
check(!connection.metaData.isReadOnly) { "Database should not be readonly." } check(!connection.metaData.isReadOnly) { "Database should not be readonly." }
checkCorrectAttachmentsContractsTableName(connection)
} }
} }
@ -245,3 +247,28 @@ fun <T : Any> rx.Observable<T>.wrapWithDatabaseTransaction(db: CordaPersistence?
} }
} }
} }
/** Check if any nested cause is of [SQLException] type. */
private fun Throwable.hasSQLExceptionCause(): Boolean =
when (cause) {
null -> false
is SQLException -> true
else -> cause?.hasSQLExceptionCause() ?: false
}
class CouldNotCreateDataSourceException(override val message: String?, override val cause: Throwable? = null) : Exception()
class IncompatibleAttachmentsContractsTableName(override val message: String?, override val cause: Throwable? = null) : Exception()
private fun checkCorrectAttachmentsContractsTableName(connection: Connection) {
val correctName = "NODE_ATTACHMENTS_CONTRACTS"
val incorrectV30Name = "NODE_ATTACHMENTS_CONTRACT_CLASS_NAME"
val incorrectV31Name = "NODE_ATTCHMENTS_CONTRACTS"
fun warning(incorrectName: String, version: String) = "The database contains the older table name $incorrectName instead of $correctName, see upgrade notes to migrate from Corda database version $version https://docs.corda.net/head/upgrade-notes.html."
if (!connection.metaData.getTables(null, null, correctName, null).next()) {
if (connection.metaData.getTables(null, null, incorrectV30Name, null).next()) { throw IncompatibleAttachmentsContractsTableName(warning(incorrectV30Name, "3.0")) }
if (connection.metaData.getTables(null, null, incorrectV31Name, null).next()) { throw IncompatibleAttachmentsContractsTableName(warning(incorrectV31Name, "3.1")) }
}
}

View File

@ -0,0 +1,67 @@
package net.corda.node.persistence
import net.corda.client.rpc.CordaRPCClient
import net.corda.core.internal.packageName
import net.corda.core.messaging.startFlow
import net.corda.core.utilities.getOrThrow
import net.corda.node.services.Permissions
import net.corda.test.node.Message
import net.corda.test.node.MessageState
import net.corda.test.node.SendMessageFlow
import net.corda.testing.core.singleIdentity
import net.corda.testing.driver.DriverParameters
import net.corda.testing.driver.driver
import net.corda.testing.driver.internal.RandomFree
import net.corda.testing.node.User
import org.junit.Test
import java.nio.file.Path
import java.sql.DriverManager
import kotlin.test.*
class FailNodeOnNotMigratedAttachmentContractsTableNameTests {
@Test
fun `node fails when detecting table name not migrated from version 3 dot 0`() {
`node fails when not detecting compatible table name`("NODE_ATTACHMENTS_CONTRACTS", "NODE_ATTACHMENTS_CONTRACT_CLASS_NAME")
}
@Test
fun `node fails when detecting table name not migrated from version 3 dot 1`() {
`node fails when not detecting compatible table name`("NODE_ATTACHMENTS_CONTRACTS", "NODE_ATTCHMENTS_CONTRACTS")
}
fun `node fails when not detecting compatible table name`(tableNameFromMapping: String, tableNameInDB: String) {
val user = User("mark", "dadada", setOf(Permissions.startFlow<SendMessageFlow>(), Permissions.invokeRpc("vaultQuery")))
val message = Message("Hello world!")
val baseDir: Path = driver(DriverParameters(startNodesInProcess = true,
portAllocation = RandomFree, extraCordappPackagesToScan = listOf(MessageState::class.packageName))) {
val (nodeName, baseDir) = {
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
val nodeName = nodeHandle.nodeInfo.singleIdentity().name
CordaRPCClient(nodeHandle.rpcAddress).start(user.username, user.password).use {
it.proxy.startFlow(::SendMessageFlow, message, defaultNotaryIdentity).returnValue.getOrThrow()
}
nodeHandle.stop()
Pair(nodeName, nodeHandle.baseDirectory)
}()
// replace the correct table name with one from the former release
DriverManager.getConnection("jdbc:h2:file://$baseDir/persistence", "sa", "").use {
it.createStatement().execute("ALTER TABLE $tableNameFromMapping RENAME TO $tableNameInDB")
it.commit()
}
assertFailsWith(net.corda.nodeapi.internal.persistence.IncompatibleAttachmentsContractsTableName::class) {
val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user)).getOrThrow()
nodeHandle.stop()
}
baseDir
}
// check that the node didn't recreated the correct table matching it's entity mapping
val (hasTableFromMapping, hasTableFromDB) = DriverManager.getConnection("jdbc:h2:file://$baseDir/persistence", "sa", "").use {
Pair(it.metaData.getTables(null, null, tableNameFromMapping, null).next(),
it.metaData.getTables(null, null, tableNameInDB, null).next())
}
assertFalse(hasTableFromMapping)
assertTrue(hasTableFromDB)
}
}

View File

@ -2,6 +2,7 @@ package net.corda.node.internal
import com.codahale.metrics.JmxReporter import com.codahale.metrics.JmxReporter
import net.corda.core.concurrent.CordaFuture import net.corda.core.concurrent.CordaFuture
import net.corda.core.internal.Emoji
import net.corda.core.internal.concurrent.openFuture import net.corda.core.internal.concurrent.openFuture
import net.corda.core.internal.concurrent.thenMatch import net.corda.core.internal.concurrent.thenMatch
import net.corda.core.internal.div import net.corda.core.internal.div
@ -74,6 +75,13 @@ open class Node(configuration: NodeConfiguration,
LoggerFactory.getLogger(loggerName).info(msg) LoggerFactory.getLogger(loggerName).info(msg)
} }
fun printWarning(message: String) {
Emoji.renderIfSupported {
println("ATTENTION: $message")
}
staticLog.warn(message)
}
internal fun failStartUp(message: String): Nothing { internal fun failStartUp(message: String): Nothing {
println(message) println(message)
println("Corda will now exit...") println("Corda will now exit...")

View File

@ -17,6 +17,7 @@ import net.corda.node.shell.InteractiveShell
import net.corda.node.utilities.registration.HTTPNetworkRegistrationService import net.corda.node.utilities.registration.HTTPNetworkRegistrationService
import net.corda.node.utilities.registration.NetworkRegistrationHelper import net.corda.node.utilities.registration.NetworkRegistrationHelper
import net.corda.nodeapi.internal.addShutdownHook import net.corda.nodeapi.internal.addShutdownHook
import net.corda.nodeapi.internal.persistence.IncompatibleAttachmentsContractsTableName
import org.fusesource.jansi.Ansi import org.fusesource.jansi.Ansi
import org.fusesource.jansi.AnsiConsole import org.fusesource.jansi.AnsiConsole
import org.slf4j.bridge.SLF4JBridgeHandler import org.slf4j.bridge.SLF4JBridgeHandler
@ -113,6 +114,10 @@ open class NodeStartup(val args: Array<String>) {
try { try {
cmdlineOptions.baseDirectory.createDirectories() cmdlineOptions.baseDirectory.createDirectories()
startNode(conf, versionInfo, startTime, cmdlineOptions) startNode(conf, versionInfo, startTime, cmdlineOptions)
} catch (e: IncompatibleAttachmentsContractsTableName) {
e.message?.let { Node.printWarning(it) }
logger.error(e.message)
return false
} catch (e: Exception) { } catch (e: Exception) {
if (e is Errors.NativeIoException && e.message?.contains("Address already in use") == true) { if (e is Errors.NativeIoException && e.message?.contains("Address already in use") == true) {
logger.error("One of the ports required by the Corda node is already in use.") logger.error("One of the ports required by the Corda node is already in use.")