[ENT-1281]: Disable database.runMigration by default and enforce database version on startup. (#264)

* [ENT-1281]: set database.runMigration=false by default and add state check at startup

* [ENT-1281]: attempt to fix tests

* [ENT-1281]: attempt to fix tests

* [ENT-1281]: set runMigration=true in the cordformation plugin

* [ENT-1281]: attempt to fix tests

* [ENT-1281]: attempt to fix tests

* [ENT-1281]: attempt to fix tests

* [ENT-1281]: fix formatting

* [ENT-1281]: typo and javadocs

* [ENT-1281]: small refactoring and added test for SchemaMigration

* [ENT-1281]: update documentation to reflect changes

* [ENT-1281]: fix tests after merge

* [ENT-1339]: for h2, allow schemas without migrations to run (#294)

* [ENT-1339]: for h2, allow schemas without migrations to run

* [ENT-1339]: fix various migration issues and change author name

* [ENT-1339]: add naming convention for migrations

* [ENT-1339]: change naming convention to use hyphens

* [ENT-1339]: change mapping of participants to be able to control the table name

* [ENT-1339]: change FK names to <=30 for oracle 11g compatibility

* [ENT-1339]: cmd line argument for migrations made consistent

* [ENT-1339]: revert abstract state superclasses

* Update db integration test setup - new tables.

* Update db integration test setup - new tables.

* [ENT-1339]: remove final from participants to allow table name config

* [ENT-1339]: shortened pk

* [ENT-1339]: revert constructor

* [ENT-1339]: change getMigrationResource api to Nullable

* fix compile error

* [ENT-1281]: fix tests after merge

* [ENT-1281]: fix tests after merge
This commit is contained in:
Tudor Malene 2018-01-10 11:32:24 +00:00 committed by GitHub
parent 3c8ebdedae
commit 12546c0a7c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 135 additions and 40 deletions

View File

@ -48,7 +48,8 @@ public class StandaloneCordaRPCJavaClientTest {
port.getAndIncrement(), port.getAndIncrement(),
port.getAndIncrement(), port.getAndIncrement(),
true, true,
Collections.singletonList(rpcUser) Collections.singletonList(rpcUser),
true
); );
@Before @Before

View File

@ -151,6 +151,7 @@ As a database migration tool, we use the open source library liquibase <http://
If migration is enabled, the database state is checked (and updated) during node startup. (After deploying a new version of the code that contains database migrations, they are executed at that point). If migration is enabled, the database state is checked (and updated) during node startup. (After deploying a new version of the code that contains database migrations, they are executed at that point).
Possible database changes range from schema changes to data changes. Possible database changes range from schema changes to data changes.
If migration is disabled (the default), then on node startup, the database "version" is checked if it is up-to-date with the deployed code.
Liquibase will create a table called ``DATABASECHANGELOG``, that will store useful information about each change ( like timestamp, description, user, md5 hash so it can't be changed, etc) Liquibase will create a table called ``DATABASECHANGELOG``, that will store useful information about each change ( like timestamp, description, user, md5 hash so it can't be changed, etc)
We can also "tag" the database at each release to make rollback easier. We can also "tag" the database at each release to make rollback easier.
@ -228,7 +229,7 @@ Usage:
Configurations: Configurations:
- To enable migration at startup, set: - To enable migration at startup, set:
- database.runMigration = true // true by default - database.runMigration = true // false by default,
Command line arguments: Command line arguments:

View File

@ -87,6 +87,7 @@ class Node(private val project: Project) : CordformNode() {
} }
private fun configureProperties() { private fun configureProperties() {
config = config.withValue("database.runMigration", ConfigValueFactory.fromAnyRef(true))
config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers)) config = config.withValue("rpcUsers", ConfigValueFactory.fromIterable(rpcUsers))
if (notary != null) { if (notary != null) {
config = config.withValue("notary", ConfigValueFactory.fromMap(notary)) config = config.withValue("notary", ConfigValueFactory.fromMap(notary))

View File

@ -17,6 +17,7 @@ import net.corda.finance.DOLLARS
import net.corda.finance.flows.CashIssueAndPaymentFlow import net.corda.finance.flows.CashIssueAndPaymentFlow
import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair import net.corda.nodeapi.internal.crypto.CertificateAndKeyPair
import net.corda.nodeapi.internal.network.NetworkParameters import net.corda.nodeapi.internal.network.NetworkParameters
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.SerializationEnvironmentRule import net.corda.testing.SerializationEnvironmentRule
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.driver.NodeHandle import net.corda.testing.driver.NodeHandle
@ -136,7 +137,7 @@ class NodeRegistrationTest : IntegrationTest() {
return NetworkManagementServer().apply { return NetworkManagementServer().apply {
start( start(
serverAddress, serverAddress,
configureDatabase(makeTestDataSourceProperties()), configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true)),
LocalSigner(intermediateCa.keyPair, arrayOf(intermediateCa.certificate, rootCaCert)), LocalSigner(intermediateCa.keyPair, arrayOf(intermediateCa.certificate, rootCaCert)),
networkParameters, networkParameters,
networkParameters?.let { NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis) }, networkParameters?.let { NetworkMapConfig(cacheTimeout = timeoutMillis, signInterval = timeoutMillis) },

View File

@ -89,7 +89,7 @@ class SigningServiceIntegrationTest {
@Test @Test
fun `Signing service signs approved CSRs`() { fun `Signing service signs approved CSRs`() {
//Start doorman server //Start doorman server
val database = configureDatabase(makeTestDataSourceProperties()) val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true))
NetworkManagementServer().use { server -> NetworkManagementServer().use { server ->
server.start(NetworkHostAndPort(HOST, 0), database, networkMapServiceParameter = null, doormanServiceParameter = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jiraConfig = null), updateNetworkParameters = null) server.start(NetworkHostAndPort(HOST, 0), database, networkMapServiceParameter = null, doormanServiceParameter = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jiraConfig = null), updateNetworkParameters = null)
@ -99,7 +99,7 @@ class SigningServiceIntegrationTest {
doReturn(ALICE_NAME).whenever(it).myLegalName doReturn(ALICE_NAME).whenever(it).myLegalName
doReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")).whenever(it).compatibilityZoneURL doReturn(URL("http://${doormanHostAndPort.host}:${doormanHostAndPort.port}")).whenever(it).compatibilityZoneURL
} }
val signingServiceStorage = DBSignedCertificateRequestStorage(configureDatabase(makeTestDataSourceProperties())) val signingServiceStorage = DBSignedCertificateRequestStorage(configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true)))
val hsmSigner = givenSignerSigningAllRequests(signingServiceStorage) val hsmSigner = givenSignerSigningAllRequests(signingServiceStorage)
// Poll the database for approved requests // Poll the database for approved requests
@ -143,7 +143,7 @@ class SigningServiceIntegrationTest {
@Test @Test
fun `DEMO - Create CSR and poll`() { fun `DEMO - Create CSR and poll`() {
//Start doorman server //Start doorman server
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig()) val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true))
NetworkManagementServer().use { server -> NetworkManagementServer().use { server ->
server.start(NetworkHostAndPort(HOST, 0), database, networkMapServiceParameter = null, doormanServiceParameter = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jiraConfig = null), updateNetworkParameters = null) server.start(NetworkHostAndPort(HOST, 0), database, networkMapServiceParameter = null, doormanServiceParameter = DoormanConfig(approveAll = true, approveInterval = 2.seconds.toMillis(), jiraConfig = null), updateNetworkParameters = null)

View File

@ -38,9 +38,7 @@ fun configureDatabase(dataSourceProperties: Properties,
val dataSource = HikariDataSource(config) val dataSource = HikariDataSource(config)
val schemas = setOf(NetworkManagementSchemaServices.SchemaV1) val schemas = setOf(NetworkManagementSchemaServices.SchemaV1)
if (databaseConfig.runMigration) { SchemaMigration(schemas, dataSource, true, databaseConfig).nodeStartup()
SchemaMigration(schemas, dataSource, true, databaseConfig.schema).runMigration()
}
return CordaPersistence(dataSource, databaseConfig, schemas, config.dataSourceProperties.getProperty("url", ""), emptyList()) return CordaPersistence(dataSource, databaseConfig, schemas, config.dataSourceProperties.getProperty("url", ""), emptyList())
} }

View File

@ -10,6 +10,7 @@ import net.corda.core.identity.CordaX500Name
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.testing.internal.createDevNodeCaCertPath import net.corda.testing.internal.createDevNodeCaCertPath
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThat
import org.bouncycastle.pkcs.PKCS10CertificationRequest import org.bouncycastle.pkcs.PKCS10CertificationRequest
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest
@ -28,7 +29,7 @@ class PersistentCertificateRequestStorageTest : TestBase() {
@Before @Before
fun startDb() { fun startDb() {
persistence = configureDatabase(makeTestDataSourceProperties()) persistence = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true))
storage = PersistentCertificateRequestStorage(persistence) storage = PersistentCertificateRequestStorage(persistence)
} }

View File

@ -12,6 +12,7 @@ import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.network.NetworkMap import net.corda.nodeapi.internal.network.NetworkMap
import net.corda.nodeapi.internal.network.SignedNetworkMap import net.corda.nodeapi.internal.network.SignedNetworkMap
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.common.internal.testNetworkParameters import net.corda.testing.common.internal.testNetworkParameters
import net.corda.testing.internal.TestNodeInfoBuilder import net.corda.testing.internal.TestNodeInfoBuilder
import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.createDevIntermediateCaCertPath
@ -37,7 +38,7 @@ class PersistentNetworkMapStorageTest : TestBase() {
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
rootCaCert = rootCa.certificate rootCaCert = rootCa.certificate
this.intermediateCa = intermediateCa this.intermediateCa = intermediateCa
persistence = configureDatabase(makeTestDataSourceProperties()) persistence = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true))
networkMapStorage = PersistentNetworkMapStorage(persistence, LocalSigner(intermediateCa.keyPair, arrayOf(intermediateCa.certificate, rootCaCert))) networkMapStorage = PersistentNetworkMapStorage(persistence, LocalSigner(intermediateCa.keyPair, arrayOf(intermediateCa.certificate, rootCaCert)))
nodeInfoStorage = PersistentNodeInfoStorage(persistence) nodeInfoStorage = PersistentNodeInfoStorage(persistence)
requestStorage = PersistentCertificateRequestStorage(persistence) requestStorage = PersistentCertificateRequestStorage(persistence)

View File

@ -14,6 +14,7 @@ import net.corda.nodeapi.internal.crypto.CertificateType
import net.corda.nodeapi.internal.crypto.X509CertificateFactory import net.corda.nodeapi.internal.crypto.X509CertificateFactory
import net.corda.nodeapi.internal.crypto.X509Utilities import net.corda.nodeapi.internal.crypto.X509Utilities
import net.corda.nodeapi.internal.persistence.CordaPersistence import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.testing.internal.TestNodeInfoBuilder import net.corda.testing.internal.TestNodeInfoBuilder
import net.corda.testing.internal.createDevIntermediateCaCertPath import net.corda.testing.internal.createDevIntermediateCaCertPath
import net.corda.testing.internal.signWith import net.corda.testing.internal.signWith
@ -41,7 +42,7 @@ class PersistentNodeInfoStorageTest : TestBase() {
val (rootCa, intermediateCa) = createDevIntermediateCaCertPath() val (rootCa, intermediateCa) = createDevIntermediateCaCertPath()
rootCaCert = rootCa.certificate rootCaCert = rootCa.certificate
this.intermediateCa = intermediateCa this.intermediateCa = intermediateCa
persistence = configureDatabase(MockServices.makeTestDataSourceProperties()) persistence = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(runMigration = true))
nodeInfoStorage = PersistentNodeInfoStorage(persistence) nodeInfoStorage = PersistentNodeInfoStorage(persistence)
requestStorage = PersistentCertificateRequestStorage(persistence) requestStorage = PersistentCertificateRequestStorage(persistence)
} }

View File

@ -18,7 +18,7 @@ const val NODE_DATABASE_PREFIX = "node_"
// This class forms part of the node config and so any changes to it must be handled with care // This class forms part of the node config and so any changes to it must be handled with care
data class DatabaseConfig( data class DatabaseConfig(
val runMigration: Boolean = true, val runMigration: Boolean = false,
val transactionIsolationLevel: TransactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ, val transactionIsolationLevel: TransactionIsolationLevel = TransactionIsolationLevel.REPEATABLE_READ,
val schema: String? = null, val schema: String? = null,
val exportHibernateJMXStatistics: Boolean = false val exportHibernateJMXStatistics: Boolean = false

View File

@ -2,6 +2,7 @@ package net.corda.nodeapi.internal.persistence
import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.ObjectMapper
import liquibase.Contexts import liquibase.Contexts
import liquibase.LabelExpression
import liquibase.Liquibase import liquibase.Liquibase
import liquibase.database.Database import liquibase.database.Database
import liquibase.database.DatabaseFactory import liquibase.database.DatabaseFactory
@ -14,17 +15,34 @@ import net.corda.core.utilities.contextLogger
import java.io.* import java.io.*
import javax.sql.DataSource import javax.sql.DataSource
class SchemaMigration(val schemas: Set<MappedSchema>, val dataSource: DataSource, val failOnMigrationMissing: Boolean, private val schemaName: String? = null) { class SchemaMigration(val schemas: Set<MappedSchema>, val dataSource: DataSource, val failOnMigrationMissing: Boolean, private val databaseConfig: DatabaseConfig) {
companion object { companion object {
private val logger = contextLogger() private val logger = contextLogger()
} }
fun generateMigrationScript(outputFile: File) = doRunMigration(PrintWriter(outputFile)) /**
* Main entry point to the schema migration.
* Called during node startup.
*/
fun nodeStartup() = if (databaseConfig.runMigration) runMigration() else checkState()
fun runMigration() = doRunMigration() /**
* will run the liquibase migration on the actual database
*/
fun runMigration() = doRunMigration(run = true, outputWriter = null, check = false)
private fun doRunMigration(outputWriter: Writer? = null) { /**
* will write the migration to the outputFile
*/
fun generateMigrationScript(outputFile: File) = doRunMigration(run = false, outputWriter = PrintWriter(outputFile), check = false)
/**
* ensures that the database is up to date with the latest migration changes
*/
fun checkState() = doRunMigration(run = false, outputWriter = null, check = true)
private fun doRunMigration(run: Boolean, outputWriter: Writer?, check: Boolean) {
// virtual file name of the changelog that includes all schemas // virtual file name of the changelog that includes all schemas
val dynamicInclude = "master.changelog.json" val dynamicInclude = "master.changelog.json"
@ -65,6 +83,7 @@ class SchemaMigration(val schemas: Set<MappedSchema>, val dataSource: DataSource
val liquibase = Liquibase(dynamicInclude, customResourceAccessor, getLiquibaseDatabase(JdbcConnection(connection))) val liquibase = Liquibase(dynamicInclude, customResourceAccessor, getLiquibaseDatabase(JdbcConnection(connection)))
val schemaName: String? = databaseConfig.schema
if (!schemaName.isNullOrBlank()) { if (!schemaName.isNullOrBlank()) {
if (liquibase.database.defaultSchemaName != schemaName) { if (liquibase.database.defaultSchemaName != schemaName) {
logger.debug("defaultSchemaName=${liquibase.database.defaultSchemaName} changed to $schemaName") logger.debug("defaultSchemaName=${liquibase.database.defaultSchemaName} changed to $schemaName")
@ -79,10 +98,16 @@ class SchemaMigration(val schemas: Set<MappedSchema>, val dataSource: DataSource
logger.info("liquibaseSchemaName=${liquibase.database.liquibaseSchemaName}") logger.info("liquibaseSchemaName=${liquibase.database.liquibaseSchemaName}")
logger.info("outputDefaultSchema=${liquibase.database.outputDefaultSchema}") logger.info("outputDefaultSchema=${liquibase.database.outputDefaultSchema}")
if (outputWriter != null) { when {
liquibase.update(Contexts(), outputWriter) run && !check -> liquibase.update(Contexts())
} else { check && !run -> {
liquibase.update(Contexts()) val unRunChanges = liquibase.listUnrunChangeSets(Contexts(), LabelExpression())
if (unRunChanges.isNotEmpty()) {
throw Exception("There are ${unRunChanges.size} outstanding database changes that need to be run. Please use the provided tools to update the database.")
}
}
(outputWriter != null) && !check && !run -> liquibase.update(Contexts(), outputWriter)
else -> throw IllegalStateException("Invalid usage.")
} }
} }
} }

View File

@ -204,14 +204,14 @@ abstract class AbstractNode(val configuration: NodeConfiguration,
fun generateDatabaseSchema(outputFile: String) { fun generateDatabaseSchema(outputFile: String) {
HikariDataSource(HikariConfig(configuration.dataSourceProperties)).use { dataSource -> HikariDataSource(HikariConfig(configuration.dataSourceProperties)).use { dataSource ->
val jdbcUrl = configuration.dataSourceProperties.getProperty("url", "") val jdbcUrl = configuration.dataSourceProperties.getProperty("url", "")
SchemaMigration(cordappLoader.cordappSchemas, dataSource, !isH2Database(jdbcUrl), configuration.database.schema).generateMigrationScript(File(outputFile)) SchemaMigration(cordappLoader.cordappSchemas, dataSource, !isH2Database(jdbcUrl), configuration.database).generateMigrationScript(File(outputFile))
} }
} }
fun runDbMigration() { fun runDbMigration() {
HikariDataSource(HikariConfig(configuration.dataSourceProperties)).use { dataSource -> HikariDataSource(HikariConfig(configuration.dataSourceProperties)).use { dataSource ->
val jdbcUrl = configuration.dataSourceProperties.getProperty("url", "") val jdbcUrl = configuration.dataSourceProperties.getProperty("url", "")
SchemaMigration(cordappLoader.cordappSchemas, dataSource, !isH2Database(jdbcUrl), configuration.database.schema).runMigration() SchemaMigration(cordappLoader.cordappSchemas, dataSource, !isH2Database(jdbcUrl), configuration.database).runMigration()
} }
} }
@ -887,7 +887,7 @@ fun configureDatabase(dataSourceProperties: Properties,
val jdbcUrl = config.dataSourceProperties.getProperty("url", "") val jdbcUrl = config.dataSourceProperties.getProperty("url", "")
if (databaseConfig.runMigration) { if (databaseConfig.runMigration) {
SchemaMigration(schemaService.schemaOptions.keys, dataSource, !isH2Database(jdbcUrl), databaseConfig.schema).runMigration() SchemaMigration(schemaService.schemaOptions.keys, dataSource, !isH2Database(jdbcUrl), databaseConfig).runMigration()
} }
return CordaPersistence(dataSource, databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, attributeConverters) return CordaPersistence(dataSource, databaseConfig, schemaService.schemaOptions.keys, jdbcUrl, attributeConverters)

View File

@ -32,7 +32,7 @@ class InteractiveShellTest {
@Before @Before
fun setup() { fun setup() {
InteractiveShell.database = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) InteractiveShell.database = configureDatabase(MockServices.makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
} }
@After @After

View File

@ -49,7 +49,7 @@ class PersistentIdentityServiceTests {
@Before @Before
fun setup() { fun setup() {
identityService = PersistentIdentityService(DEV_ROOT_CA.certificate) identityService = PersistentIdentityService(DEV_ROOT_CA.certificate)
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), identityService) database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), identityService)
} }
@After @After

View File

@ -81,7 +81,7 @@ class ArtemisMessagingTests {
doReturn(true).whenever(it).useAMQPBridges doReturn(true).whenever(it).useAMQPBridges
} }
LogHelper.setLevel(PersistentUniquenessProvider::class) LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()), rigorousMock()) networkMapCache = NetworkMapCacheImpl(PersistentNetworkMapCache(database, emptyList()), rigorousMock())
} }

View File

@ -45,7 +45,7 @@ class DBCheckpointStorageTests {
@Before @Before
fun setUp() { fun setUp() {
LogHelper.setLevel(PersistentUniquenessProvider::class) LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
newCheckpointStorage() newCheckpointStorage()
} }

View File

@ -40,7 +40,7 @@ class DBTransactionStorageTests {
fun setUp() { fun setUp() {
LogHelper.setLevel(PersistentUniquenessProvider::class) LogHelper.setLevel(PersistentUniquenessProvider::class)
val dataSourceProps = makeTestDataSourceProperties() val dataSourceProps = makeTestDataSourceProperties()
database = configureDatabase(dataSourceProps, DatabaseConfig(), rigorousMock()) database = configureDatabase(dataSourceProps, DatabaseConfig(runMigration = true), rigorousMock())
newTransactionStorage() newTransactionStorage()
} }

View File

@ -108,7 +108,7 @@ class HibernateConfigurationTest {
} }
} }
val schemaService = NodeSchemaService(extraSchemas = setOf(CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3, DummyLinearStateSchemaV1, DummyLinearStateSchemaV2, DummyDealStateSchemaV1 )) val schemaService = NodeSchemaService(extraSchemas = setOf(CashSchemaV1, SampleCashSchemaV2, SampleCashSchemaV3, DummyLinearStateSchemaV1, DummyLinearStateSchemaV2, DummyDealStateSchemaV1 ))
database = configureDatabase(dataSourceProps, DatabaseConfig(), identityService, schemaService) database = configureDatabase(dataSourceProps, DatabaseConfig(runMigration = true), identityService, schemaService)
database.transaction { database.transaction {
hibernateConfig = database.hibernateConfig hibernateConfig = database.hibernateConfig
// `consumeCash` expects we can self-notarise transactions // `consumeCash` expects we can self-notarise transactions

View File

@ -44,7 +44,7 @@ class NodeAttachmentStorageTest {
LogHelper.setLevel(PersistentUniquenessProvider::class) LogHelper.setLevel(PersistentUniquenessProvider::class)
val dataSourceProperties = makeTestDataSourceProperties() val dataSourceProperties = makeTestDataSourceProperties()
database = configureDatabase(dataSourceProperties, DatabaseConfig(), rigorousMock()) database = configureDatabase(dataSourceProperties, DatabaseConfig(runMigration = true), rigorousMock())
fs = Jimfs.newFileSystem(Configuration.unix()) fs = Jimfs.newFileSystem(Configuration.unix())
} }

View File

@ -0,0 +1,59 @@
package net.corda.node.services.persistence
import com.zaxxer.hikari.HikariConfig
import com.zaxxer.hikari.HikariDataSource
import net.corda.node.internal.configureDatabase
import net.corda.node.services.schema.NodeSchemaService
import net.corda.nodeapi.internal.persistence.CordaPersistence
import net.corda.nodeapi.internal.persistence.DatabaseConfig
import net.corda.nodeapi.internal.persistence.SchemaMigration
import net.corda.testing.internal.rigorousMock
import net.corda.testing.node.MockServices
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.Test
import java.math.BigInteger
class SchemaMigrationTest {
@Test
fun `Ensure that runMigration is disabled by default`() {
assertThat(DatabaseConfig().runMigration).isFalse()
}
@Test
fun `Migration is run when runMigration is disabled, and database is H2`() {
val dataSourceProps = MockServices.makeTestDataSourceProperties()
val db = configureDatabase(dataSourceProps, DatabaseConfig(runMigration = false), rigorousMock())
checkMigrationRun(db)
}
@Test
fun `Migration is run when runMigration is enabled`() {
val dataSourceProps = MockServices.makeTestDataSourceProperties()
val db = configureDatabase(dataSourceProps, DatabaseConfig(runMigration = true), rigorousMock())
checkMigrationRun(db)
}
@Test
fun `Verification passes when migration is run as a separate step`() {
val schemaService = NodeSchemaService()
val dataSourceProps = MockServices.makeTestDataSourceProperties()
//run the migration on the database
val migration = SchemaMigration(schemaService.schemaOptions.keys, HikariDataSource(HikariConfig(dataSourceProps)), true, DatabaseConfig())
migration.runMigration()
//start the node with "runMigration = false" and check that it started correctly
val db = configureDatabase(dataSourceProps, DatabaseConfig(runMigration = false), rigorousMock(), schemaService)
checkMigrationRun(db)
}
private fun checkMigrationRun(db: CordaPersistence) {
//check that the hibernate_sequence was created which means the migration was run
db.transaction {
val value = this.session.createNativeQuery("SELECT NEXT VALUE FOR hibernate_sequence").uniqueResult() as BigInteger
assertThat(value).isGreaterThan(BigInteger.ZERO)
}
}
}

View File

@ -69,7 +69,7 @@ class HibernateObserverTests {
return parent return parent
} }
} }
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock(), schemaService) val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock(), schemaService)
HibernateObserver.install(rawUpdatesPublisher, database.hibernateConfig, schemaService) HibernateObserver.install(rawUpdatesPublisher, database.hibernateConfig, schemaService)
database.transaction { database.transaction {
val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party val MEGA_CORP = TestIdentity(CordaX500Name("MegaCorp", "London", "GB")).party

View File

@ -88,7 +88,7 @@ class DistributedImmutableMapTests {
private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture<Member> { private fun createReplica(myAddress: NetworkHostAndPort, clusterAddress: NetworkHostAndPort? = null): CompletableFuture<Member> {
val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build() val storage = Storage.builder().withStorageLevel(StorageLevel.MEMORY).build()
val address = Address(myAddress.host, myAddress.port) val address = Address(myAddress.host, myAddress.port)
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
databases.add(database) databases.add(database)
val stateMachineFactory = { DistributedImmutableMap(database, RaftUniquenessProvider.Companion::createMap) } val stateMachineFactory = { DistributedImmutableMap(database, RaftUniquenessProvider.Companion::createMap) }

View File

@ -29,7 +29,7 @@ class PersistentUniquenessProviderTests {
@Before @Before
fun setUp() { fun setUp() {
LogHelper.setLevel(PersistentUniquenessProvider::class) LogHelper.setLevel(PersistentUniquenessProvider::class)
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
} }
@After @After

View File

@ -131,7 +131,7 @@ open class VaultQueryTests {
@Ignore @Ignore
@Test @Test
fun createPersistentTestDb() { fun createPersistentTestDb() {
val database = configureDatabase(makePersistentDataSourceProperties(), DatabaseConfig(), identitySvc) val database = configureDatabase(makePersistentDataSourceProperties(), DatabaseConfig(runMigration = true), identitySvc)
setUpDb(database, 5000) setUpDb(database, 5000)
database.close() database.close()

View File

@ -22,7 +22,7 @@ class ObservablesTests {
private val toBeClosed = mutableListOf<Closeable>() private val toBeClosed = mutableListOf<Closeable>()
private fun createDatabase(): CordaPersistence { private fun createDatabase(): CordaPersistence {
val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) val database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
toBeClosed += database toBeClosed += database
return database return database
} }

View File

@ -74,7 +74,7 @@ class NodeInterestRatesTest {
@Before @Before
fun setUp() { fun setUp() {
database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(), rigorousMock()) database = configureDatabase(makeTestDataSourceProperties(), DatabaseConfig(runMigration = true), rigorousMock())
database.transaction { database.transaction {
oracle = createMockCordaService(services, NodeInterestRates::Oracle) oracle = createMockCordaService(services, NodeInterestRates::Oracle)
oracle.knownFixes = TEST_DATA oracle.knownFixes = TEST_DATA

View File

@ -107,7 +107,7 @@ open class MockServices private constructor(
val transactionIsolationLevel = if (config.hasPath(TRANSACTION_ISOLATION_LEVEL)) TransactionIsolationLevel.valueOf(config.getString(TRANSACTION_ISOLATION_LEVEL)) val transactionIsolationLevel = if (config.hasPath(TRANSACTION_ISOLATION_LEVEL)) TransactionIsolationLevel.valueOf(config.getString(TRANSACTION_ISOLATION_LEVEL))
else TransactionIsolationLevel.READ_COMMITTED else TransactionIsolationLevel.READ_COMMITTED
val schema = if (config.hasPath(SCHEMA)) config.getString(SCHEMA) else "" val schema = if (config.hasPath(SCHEMA)) config.getString(SCHEMA) else ""
return DatabaseConfig(transactionIsolationLevel = transactionIsolationLevel, schema = schema) return DatabaseConfig(runMigration = true, transactionIsolationLevel = transactionIsolationLevel, schema = schema)
} }
/** /**

View File

@ -209,6 +209,7 @@ class DriverDSLImpl(
baseDirectory = baseDirectory(name), baseDirectory = baseDirectory(name),
allowMissingConfig = true, allowMissingConfig = true,
configOverrides = configOf( configOverrides = configOf(
"database" to mapOf("runMigration" to "true"),
"myLegalName" to name.toString(), "myLegalName" to name.toString(),
"p2pAddress" to p2pAddress.toString(), "p2pAddress" to p2pAddress.toString(),
"rpcAddress" to rpcAddress.toString(), "rpcAddress" to rpcAddress.toString(),
@ -227,6 +228,7 @@ class DriverDSLImpl(
baseDirectory = baseDirectory, baseDirectory = baseDirectory,
allowMissingConfig = true, allowMissingConfig = true,
configOverrides = configOf( configOverrides = configOf(
"database" to mapOf("runMigration" to "true"),
"p2pAddress" to "localhost:1222", // required argument, not really used "p2pAddress" to "localhost:1222", // required argument, not really used
"compatibilityZoneURL" to compatibilityZoneURL.toString(), "compatibilityZoneURL" to compatibilityZoneURL.toString(),
"myLegalName" to providedName.toString()) "myLegalName" to providedName.toString())
@ -313,7 +315,8 @@ class DriverDSLImpl(
baseDirectory = baseDirectory(name), baseDirectory = baseDirectory(name),
allowMissingConfig = true, allowMissingConfig = true,
configOverrides = cordform.config + rpcAddress + notary + mapOf( configOverrides = cordform.config + rpcAddress + notary + mapOf(
"rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers "rpcUsers" to if (rpcUsers.isEmpty()) defaultRpcUserList else rpcUsers,
"database" to mapOf("runMigration" to "true")
) )
)) ))
return startNodeInternal(config, webAddress, null, "200m", localNetworkMap) return startNodeInternal(config, webAddress, null, "200m", localNetworkMap)

View File

@ -91,6 +91,7 @@ abstract class NodeBasedTest(private val cordappPackages: List<String> = emptyLi
baseDirectory = baseDirectory, baseDirectory = baseDirectory,
allowMissingConfig = true, allowMissingConfig = true,
configOverrides = configOf( configOverrides = configOf(
"database" to mapOf("runMigration" to "true"),
"myLegalName" to legalName.toString(), "myLegalName" to legalName.toString(),
"p2pAddress" to p2pAddress, "p2pAddress" to p2pAddress,
"rpcAddress" to localPort[1].toString(), "rpcAddress" to localPort[1].toString(),

View File

@ -14,7 +14,8 @@ class NodeConfig(
val rpcPort: Int, val rpcPort: Int,
val webPort: Int, val webPort: Int,
val isNotary: Boolean, val isNotary: Boolean,
val users: List<User> val users: List<User>,
val runMigration: Boolean = true
) { ) {
companion object { companion object {
val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false) val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false)
@ -34,6 +35,7 @@ class NodeConfig(
.withValue("webAddress", addressValueFor(webPort)) .withValue("webAddress", addressValueFor(webPort))
.withValue("rpcAddress", addressValueFor(rpcPort)) .withValue("rpcAddress", addressValueFor(rpcPort))
.withValue("rpcUsers", valueFor(users.map(User::toMap).toList())) .withValue("rpcUsers", valueFor(users.map(User::toMap).toList()))
.withValue("database", valueFor(mapOf("runMigration" to runMigration)))
.withValue("useTestClock", valueFor(true)) .withValue("useTestClock", valueFor(true))
return if (isNotary) { return if (isNotary) {
config.withValue("notary", ConfigValueFactory.fromMap(mapOf("validating" to true))) config.withValue("notary", ConfigValueFactory.fromMap(mapOf("validating" to true)))