ENT-8983 Upgrade H2 and liquibase to latest version (#7298)

This commit is contained in:
Mahmoud Almahroum
2023-03-03 15:10:25 +00:00
committed by GitHub
parent c777e77962
commit 1a0d354903
6 changed files with 76 additions and 39 deletions

View File

@ -77,9 +77,9 @@ mockitoKotlinVersion=1.6.0
hamkrestVersion=1.7.0.0 hamkrestVersion=1.7.0.0
joptSimpleVersion=5.0.2 joptSimpleVersion=5.0.2
jansiVersion=1.18 jansiVersion=1.18
hibernateVersion=5.4.32.Final hibernateVersion=5.6.5.Final
# h2Version - Update docs if renamed or removed. # h2Version - Update docs if renamed or removed.
h2Version=1.4.199 h2Version=2.1.212
rxjavaVersion=1.3.8 rxjavaVersion=1.3.8
dokkaVersion=0.10.1 dokkaVersion=0.10.1
eddsaVersion=0.3.0 eddsaVersion=0.3.0
@ -88,7 +88,7 @@ commonsCollectionsVersion=4.3
beanutilsVersion=1.9.4 beanutilsVersion=1.9.4
shiroVersion=1.10.0 shiroVersion=1.10.0
hikariVersion=3.3.1 hikariVersion=3.3.1
liquibaseVersion=3.6.3 liquibaseVersion=4.18.0
dockerComposeRuleVersion=1.5.0 dockerComposeRuleVersion=1.5.0
seleniumVersion=3.141.59 seleniumVersion=3.141.59
ghostdriverVersion=2.1.0 ghostdriverVersion=2.1.0

Binary file not shown.

View File

@ -4,9 +4,13 @@ import com.fasterxml.jackson.databind.ObjectMapper
import liquibase.Contexts import liquibase.Contexts
import liquibase.LabelExpression import liquibase.LabelExpression
import liquibase.Liquibase import liquibase.Liquibase
import liquibase.Scope
import liquibase.ThreadLocalScopeManager
import liquibase.database.jvm.JdbcConnection import liquibase.database.jvm.JdbcConnection
import liquibase.exception.LiquibaseException import liquibase.exception.LiquibaseException
import liquibase.resource.ClassLoaderResourceAccessor import liquibase.resource.ClassLoaderResourceAccessor
import liquibase.resource.Resource
import liquibase.resource.URIResource
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
import net.corda.core.schemas.MappedSchema import net.corda.core.schemas.MappedSchema
import net.corda.core.utilities.contextLogger import net.corda.core.utilities.contextLogger
@ -14,8 +18,10 @@ import net.corda.nodeapi.internal.MigrationHelpers.getMigrationResource
import net.corda.nodeapi.internal.cordapp.CordappLoader import net.corda.nodeapi.internal.cordapp.CordappLoader
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.InputStream import java.io.InputStream
import java.net.URI
import java.nio.file.Path import java.nio.file.Path
import java.sql.Connection import java.sql.Connection
import java.util.Collections
import java.util.concurrent.locks.ReentrantLock import java.util.concurrent.locks.ReentrantLock
import javax.sql.DataSource import javax.sql.DataSource
import kotlin.concurrent.withLock import kotlin.concurrent.withLock
@ -36,6 +42,10 @@ open class SchemaMigration(
const val NODE_BASE_DIR_KEY = "liquibase.nodeDaseDir" const val NODE_BASE_DIR_KEY = "liquibase.nodeDaseDir"
const val NODE_X500_NAME = "liquibase.nodeName" const val NODE_X500_NAME = "liquibase.nodeName"
val loader = ThreadLocal<CordappLoader>() val loader = ThreadLocal<CordappLoader>()
init {
Scope.setScopeManager(ThreadLocalScopeManager())
}
@JvmStatic @JvmStatic
protected val mutex = ReentrantLock() protected val mutex = ReentrantLock()
} }
@ -46,31 +56,31 @@ open class SchemaMigration(
private val classLoader = cordappLoader?.appClassLoader ?: Thread.currentThread().contextClassLoader private val classLoader = cordappLoader?.appClassLoader ?: Thread.currentThread().contextClassLoader
/** /**
* Will run the Liquibase migration on the actual database. * Will run the Liquibase migration on the actual database.
* @param existingCheckpoints Whether checkpoints exist that would prohibit running a migration * @param existingCheckpoints Whether checkpoints exist that would prohibit running a migration
* @param schemas The set of MappedSchemas to check * @param schemas The set of MappedSchemas to check
* @param forceThrowOnMissingMigration throws an exception if a mapped schema is missing the migration resource. Can be set to false * @param forceThrowOnMissingMigration throws an exception if a mapped schema is missing the migration resource. Can be set to false
* when allowing hibernate to create missing schemas in dev or tests. * when allowing hibernate to create missing schemas in dev or tests.
*/ */
fun runMigration(existingCheckpoints: Boolean, schemas: Set<MappedSchema>, forceThrowOnMissingMigration: Boolean) { fun runMigration(existingCheckpoints: Boolean, schemas: Set<MappedSchema>, forceThrowOnMissingMigration: Boolean) {
val resourcesAndSourceInfo = prepareResources(schemas, forceThrowOnMissingMigration) val resourcesAndSourceInfo = prepareResources(schemas, forceThrowOnMissingMigration)
Scope.enter(mapOf(Scope.Attr.classLoader.name to classLoader))
// current version of Liquibase appears to be non-threadsafe // current version of Liquibase appears to be non-threadsafe
// this is apparent when multiple in-process nodes are all running migrations simultaneously // this is apparent when multiple in-process nodes are all running migrations simultaneously
mutex.withLock { mutex.withLock {
dataSource.connection.use { connection -> dataSource.connection.use { connection ->
val (runner, _, shouldBlockOnCheckpoints) = prepareRunner(connection, resourcesAndSourceInfo) val (runner, _, shouldBlockOnCheckpoints) = prepareRunner(connection, resourcesAndSourceInfo)
if (shouldBlockOnCheckpoints && existingCheckpoints) if (shouldBlockOnCheckpoints && existingCheckpoints)
throw CheckpointsException() throw CheckpointsException()
try { try {
runner.update(Contexts().toString()) runner.update(Contexts().toString())
} catch (exp: LiquibaseException) { } catch (exp: LiquibaseException) {
throw DatabaseMigrationException(exp.message, exp) throw DatabaseMigrationException(exp.message, exp)
} }
} }
} }
} }
/** /**
* Ensures that the database is up to date with the latest migration changes. * Ensures that the database is up to date with the latest migration changes.
@ -98,7 +108,7 @@ open class SchemaMigration(
* @param forceThrowOnMissingMigration throws an exception if a mapped schema is missing the migration resource. Can be set to false * @param forceThrowOnMissingMigration throws an exception if a mapped schema is missing the migration resource. Can be set to false
* when allowing hibernate to create missing schemas in dev or tests. * when allowing hibernate to create missing schemas in dev or tests.
*/ */
fun getPendingChangesCount(schemas: Set<MappedSchema>, forceThrowOnMissingMigration: Boolean) : Int { fun getPendingChangesCount(schemas: Set<MappedSchema>, forceThrowOnMissingMigration: Boolean): Int {
val resourcesAndSourceInfo = prepareResources(schemas, forceThrowOnMissingMigration) val resourcesAndSourceInfo = prepareResources(schemas, forceThrowOnMissingMigration)
// current version of Liquibase appears to be non-threadsafe // current version of Liquibase appears to be non-threadsafe
@ -140,19 +150,42 @@ open class SchemaMigration(
/** Create a resource accessor that aggregates the changelogs included in the schemas into one dynamic stream. */ /** Create a resource accessor that aggregates the changelogs included in the schemas into one dynamic stream. */
protected class CustomResourceAccessor(val dynamicInclude: String, val changelogList: List<String?>, classLoader: ClassLoader) : protected class CustomResourceAccessor(val dynamicInclude: String, val changelogList: List<String?>, classLoader: ClassLoader) :
ClassLoaderResourceAccessor(classLoader) { ClassLoaderResourceAccessor(classLoader) {
override fun getResourcesAsStream(path: String): Set<InputStream> { override fun getAll(path: String?): List<Resource> {
if (path == dynamicInclude) { if (path == dynamicInclude) {
// Create a map in Liquibase format including all migration files.
val includeAllFiles = mapOf("databaseChangeLog"
to changelogList.filterNotNull().map { file -> mapOf("include" to mapOf("file" to file)) })
// Transform it to json.
val includeAllFilesJson = ObjectMapper().writeValueAsBytes(includeAllFiles)
// Return the json as a stream. // Return the json as a stream.
return setOf(ByteArrayInputStream(includeAllFilesJson)) val inputStream = getPathAsStream()
val resource = object : URIResource(path, URI(path)) {
override fun openInputStream(): InputStream {
return inputStream
}
}
return Collections.singletonList(resource)
} }
return super.getResourcesAsStream(path)?.take(1)?.toSet() ?: emptySet() // Take 1 resource due to LiquidBase find duplicate files which throws an error
return super.getAll(path).take(1)
}
override fun get(path: String?): Resource {
if (path == dynamicInclude) {
// Return the json as a stream.
val inputStream = getPathAsStream()
return object : URIResource(path, URI(path)) {
override fun openInputStream(): InputStream {
return inputStream
}
}
}
return super.get(path)
}
private fun getPathAsStream(): InputStream {
// Create a map in Liquibase format including all migration files.
val includeAllFiles = mapOf("databaseChangeLog"
to changelogList.filterNotNull().map { file -> mapOf("include" to mapOf("file" to file)) })
val includeAllFilesJson = ObjectMapper().writeValueAsBytes(includeAllFiles)
return ByteArrayInputStream(includeAllFilesJson)
} }
} }
@ -184,6 +217,7 @@ open class SchemaMigration(
if (ourName != null) { if (ourName != null) {
System.setProperty(NODE_X500_NAME, ourName.toString()) System.setProperty(NODE_X500_NAME, ourName.toString())
} }
Scope.enter(mapOf(Scope.Attr.classLoader.name to classLoader))
val customResourceAccessor = CustomResourceAccessor(dynamicInclude, changelogList, classLoader) val customResourceAccessor = CustomResourceAccessor(dynamicInclude, changelogList, classLoader)
checkResourcesInClassPath(changelogList) checkResourcesInClassPath(changelogList)
return listOf(Pair(customResourceAccessor, "")) return listOf(Pair(customResourceAccessor, ""))

View File

@ -8,6 +8,7 @@ import liquibase.database.Database
import liquibase.database.core.H2Database import liquibase.database.core.H2Database
import liquibase.database.jvm.JdbcConnection import liquibase.database.jvm.JdbcConnection
import liquibase.resource.ClassLoaderResourceAccessor import liquibase.resource.ClassLoaderResourceAccessor
import liquibase.resource.Resource
import net.corda.core.crypto.Crypto import net.corda.core.crypto.Crypto
import net.corda.core.crypto.toStringShort import net.corda.core.crypto.toStringShort
import net.corda.core.identity.CordaX500Name import net.corda.core.identity.CordaX500Name
@ -84,7 +85,9 @@ class IdenityServiceKeyRotationMigrationTest {
persist(charlie2.party.dbParty()) persist(charlie2.party.dbParty())
Liquibase("migration/node-core.changelog-v20.xml", object : ClassLoaderResourceAccessor() { Liquibase("migration/node-core.changelog-v20.xml", object : ClassLoaderResourceAccessor() {
override fun getResourcesAsStream(path: String) = super.getResourcesAsStream(path)?.firstOrNull()?.let { setOf(it) } override fun getAll(path: String?): List<Resource> {
return super.getAll(path).take(1).toList()
}
}, liquibaseDB).update(Contexts().toString()) }, liquibaseDB).update(Contexts().toString())
val dummyKey = Crypto.generateKeyPair().public val dummyKey = Crypto.generateKeyPair().public

View File

@ -114,7 +114,7 @@ open class MockServices private constructor(
} }
val props = Properties() val props = Properties()
props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource") props.setProperty("dataSourceClassName", "org.h2.jdbcx.JdbcDataSource")
props.setProperty("dataSource.url", "jdbc:h2:file:$dbPath;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE") props.setProperty("dataSource.url", "jdbc:h2:file:$dbPath;NON_KEYWORDS=KEY,VALUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE")
props.setProperty("dataSource.user", "sa") props.setProperty("dataSource.user", "sa")
props.setProperty("dataSource.password", "") props.setProperty("dataSource.password", "")
return props return props