Corda now works with H2 without the need to allow Hibernate to create the database automatically. (#2124)

[CORDA-815]: Corda now instructs Hibernate to either adjust or validate the schema based on `devMode` property.

Also renamed property `database.initDatabase` to `database.createSchemaAutomatically`.

* [CORDA-815]: Renamed database.initDatabase to database.adjustSchemas.

* Code review changes: removed property `database.initDatabase` altogether.

* Code review changes: removed property `database.initDatabase` altogether.

* Code review changes: removed property `database.initDatabase` altogether.

* Code review changes: removed property `database.initDatabase` altogether.
This commit is contained in:
Michele Sollecito 2017-11-28 17:33:02 +00:00 committed by GitHub
parent 74bf00c155
commit cb1fa2e017
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 50 additions and 12 deletions

View File

@ -6,6 +6,9 @@ from the previous milestone release.
UNRELEASED
----------
* Removed confusing property database.initDatabase, enabling its guarded behaviour with the dev-mode.
In devMode Hibernate will try to create or update database schemas, otherwise it will expect relevant schemas to be present
in the database (pre configured via DDL scripts or equivalent), and validate these are correct.
* ``AttachmentStorage`` now allows providing metadata on attachments upload - username and filename, currently as plain
strings. Those can be then used for querying, utilizing ``queryAttachments`` method of the same interface.

View File

@ -70,7 +70,6 @@ path to the node's base directory.
:database: Database configuration:
:initDatabase: Boolean on whether to initialise the database or just validate the schema. Defaults to true.
:serverNameTablePrefix: Prefix string to apply to all the database tables. The default is no prefix.
:transactionIsolationLevel: Transaction isolation level as defined by the ``TRANSACTION_`` constants in
``java.sql.Connection``, but without the "TRANSACTION_" prefix. Defaults to REPEATABLE_READ.
@ -141,7 +140,9 @@ path to the node's base directory.
:devMode: This flag sets the node to run in development mode. On startup, if the keystore ``<workspace>/certificates/sslkeystore.jks``
does not exist, a developer keystore will be used if ``devMode`` is true. The node will exit if ``devMode`` is false
and the keystore does not exist. ``devMode`` also turns on background checking of flow checkpoints to shake out any
bugs in the checkpointing process.
bugs in the checkpointing process. Also, if ``devMode`` is true, Hibernate will try to automatically create the schema required by Corda
or update an existing schema in the SQL database; if ``devMode`` is false, Hibernate will simply validate an existing schema
failing on node start if this schema is either not present or not compatible.
:detectPublicIp: This flag toggles the auto IP detection behaviour, it is enabled by default. On startup the node will
attempt to discover its externally visible IP address first by looking for any public addresses on its network

View File

@ -65,6 +65,40 @@ class NodeStatePersistenceTests {
val retrievedMessage = stateAndRef!!.state.data.message
assertEquals(message, retrievedMessage)
}
@Test
fun `persistent state survives node restart without reinitialising database schema`() {
// Temporary disable this test when executed on Windows. It is known to be sporadically failing.
// More investigation is needed to establish why.
assumeFalse(System.getProperty("os.name").toLowerCase().startsWith("win"))
val user = User("mark", "dadada", setOf(startFlow<SendMessageFlow>(), invokeRpc("vaultQuery")))
val message = Message("Hello world!")
val stateAndRef: StateAndRef<MessageState>? = driver(isDebug = true, startNodesInProcess = isQuasarAgentSpecified()) {
val nodeName = {
val nodeHandle = startNode(rpcUsers = listOf(user)).getOrThrow()
val nodeName = nodeHandle.nodeInfo.chooseIdentity().name
// Ensure the notary node has finished starting up, before starting a flow that needs a notary
defaultNotaryNode.getOrThrow()
nodeHandle.rpcClientToNode().start(user.username, user.password).use {
it.proxy.startFlow(::SendMessageFlow, message, defaultNotaryIdentity).returnValue.getOrThrow()
}
nodeHandle.stop()
nodeName
}()
val nodeHandle = startNode(providedName = nodeName, rpcUsers = listOf(user), customOverrides = mapOf("devMode" to "false")).getOrThrow()
val result = nodeHandle.rpcClientToNode().start(user.username, user.password).use {
val page = it.proxy.vaultQuery(MessageState::class.java)
page.states.singleOrNull()
}
nodeHandle.stop()
result
}
assertNotNull(stateAndRef)
val retrievedMessage = stateAndRef!!.state.data.message
assertEquals(message, retrievedMessage)
}
}
fun isQuasarAgentSpecified(): Boolean {

View File

@ -79,7 +79,9 @@ class CordappLoader private constructor(private val cordappJarPaths: List<Restri
*/
@VisibleForTesting
fun createDefaultWithTestPackages(configuration: NodeConfiguration, testPackages: List<String>): CordappLoader {
check(configuration.devMode) { "Package scanning can only occur in dev mode" }
if (!configuration.devMode) {
logger.warn("Package scanning should only occur in dev mode!")
}
val paths = getCordappsInDirectory(getCordappsPath(configuration.baseDirectory)) + testPackages.flatMap(this::createScanPackage)
return cordappLoadersCache.computeIfAbsent(paths, { CordappLoader(paths) })
}

View File

@ -19,7 +19,6 @@ interface NodeConfiguration : NodeSSLConfiguration {
val emailAddress: String
val exportJMXto: String
val dataSourceProperties: Properties
val database: DatabaseConfig
val rpcUsers: List<User>
val devMode: Boolean
val devModeOptions: DevModeOptions?
@ -38,12 +37,13 @@ interface NodeConfiguration : NodeSSLConfiguration {
val useTestClock: Boolean get() = false
val detectPublicIp: Boolean get() = true
val sshd: SSHDConfiguration?
val database: DatabaseConfig
}
data class DevModeOptions(val disableCheckpointChecker: Boolean = false)
data class DatabaseConfig(
val initDatabase: Boolean = true,
val initialiseSchema: Boolean = true,
val serverNameTablePrefix: String = "",
val transactionIsolationLevel: TransactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ
)
@ -108,7 +108,6 @@ data class NodeConfigurationImpl(
override val keyStorePassword: String,
override val trustStorePassword: String,
override val dataSourceProperties: Properties,
override val database: DatabaseConfig = DatabaseConfig(),
override val compatibilityZoneURL: URL? = null,
override val rpcUsers: List<User>,
override val verifierType: VerifierType,
@ -130,9 +129,9 @@ data class NodeConfigurationImpl(
override val activeMQServer: ActiveMqServerConfiguration,
// TODO See TODO above. Rename this to nodeInfoPollingFrequency and make it of type Duration
override val additionalNodeInfoPollingFrequencyMsec: Long = 5.seconds.toMillis(),
override val sshd: SSHDConfiguration? = null
) : NodeConfiguration {
override val sshd: SSHDConfiguration? = null,
override val database: DatabaseConfig = DatabaseConfig(initialiseSchema = devMode)
) : NodeConfiguration {
override val exportJMXto: String get() = "http"
init {

View File

@ -54,7 +54,7 @@ class HibernateConfiguration(val schemaService: SchemaService, private val datab
// necessarily remain and would likely be replaced by something like Liquibase. For now it is very convenient though.
// TODO: replace auto schema generation as it isn't intended for production use, according to Hibernate docs.
val config = Configuration(metadataSources).setProperty("hibernate.connection.provider_class", NodeDatabaseConnectionProvider::class.java.name)
.setProperty("hibernate.hbm2ddl.auto", if (databaseConfig.initDatabase) "update" else "validate")
.setProperty("hibernate.hbm2ddl.auto", if (databaseConfig.initialiseSchema) "update" else "validate")
.setProperty("hibernate.format_sql", "true")
.setProperty("hibernate.connection.isolation", databaseConfig.transactionIsolationLevel.jdbcValue.toString())

View File

@ -90,6 +90,7 @@ object VaultSchemaV1 : MappedSchema(schemaFamily = VaultSchema.javaClass, versio
var externalId: String?,
@Column(name = "uuid", nullable = false)
@Type(type = "uuid-char")
var uuid: UUID
) : PersistentState() {
constructor(uid: UniqueIdentifier, _participants: List<AbstractParty>) :

View File

@ -11,7 +11,6 @@ dataSourceProperties = {
}
database = {
transactionIsolationLevel = "REPEATABLE_READ"
initDatabase = true
}
devMode = true
useHTTPS = false

View File

@ -38,7 +38,6 @@ class NodeConfigurationImplTest {
keyStorePassword = "cordacadevpass",
trustStorePassword = "trustpass",
dataSourceProperties = makeTestDataSourceProperties(ALICE.name.organisation),
database = DatabaseConfig(),
rpcUsers = emptyList(),
verifierType = VerifierType.InMemory,
useHTTPS = false,