mirror of
https://github.com/corda/corda.git
synced 2024-12-28 16:58:55 +00:00
[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:
parent
3c8ebdedae
commit
12546c0a7c
@ -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
|
||||||
|
@ -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:
|
||||||
|
|
||||||
|
@ -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))
|
||||||
|
@ -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) },
|
||||||
|
@ -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)
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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
|
||||||
|
@ -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) }
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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()
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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)
|
||||||
|
@ -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(),
|
||||||
|
@ -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)))
|
||||||
|
Loading…
Reference in New Issue
Block a user