Skip to content

Commit de40ab7

Browse files
author
Zen Yui
authored
auto create COS schema on boot (#357)
1 parent 8fe0de7 commit de40ab7

File tree

6 files changed

+95
-12
lines changed

6 files changed

+95
-12
lines changed

code/migration/src/main/scala/com/namely/chiefofstate/migration/Migrator.scala

Lines changed: 28 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@ import scala.concurrent.{ Await, Future }
1818
import scala.concurrent.duration.Duration
1919
import scala.util.{ Success, Try }
2020

21-
class Migrator(val journalDbConfig: DatabaseConfig[JdbcProfile]) {
21+
/**
22+
* Runs the provided migrations
23+
*
24+
* @param journalDbConfig a jdbc config
25+
* @param schema the schema name
26+
*/
27+
class Migrator(val journalDbConfig: DatabaseConfig[JdbcProfile], schema: String) {
2228
val logger: Logger = Migrator.logger
2329

2430
// create the priority queue of versions sorted by version number
@@ -53,9 +59,11 @@ class Migrator(val journalDbConfig: DatabaseConfig[JdbcProfile]) {
5359
* runs before all migration steps, used to configure the migrator state, etc.
5460
*/
5561
private[migration] def beforeAll(): Try[Unit] = {
56-
// create the versions table
62+
// create the schema
5763
Migrator
58-
.createMigrationsTable(journalDbConfig)
64+
.createSchema(journalDbConfig, schema)
65+
// create the versions table
66+
.flatMap(_ => Migrator.createMigrationsTable(journalDbConfig))
5967
// set initial version if provided
6068
.flatMap(_ => Migrator.setInitialVersion(journalDbConfig))
6169
}
@@ -240,4 +248,21 @@ object Migrator {
240248
}
241249
}
242250
}
251+
252+
/**
253+
* creates the COS schema
254+
*
255+
* @param journalJdbcConfig a journal jdbc config
256+
* @return success/failure
257+
*/
258+
private[migration] def createSchema(journalJdbcConfig: DatabaseConfig[JdbcProfile], schema: String): Try[Unit] = Try {
259+
logger.info(s"creating schema '$schema' if not exists")
260+
// create the schema
261+
val conn = journalJdbcConfig.db.source.createConnection()
262+
try {
263+
conn.createStatement().execute(s"create schema if not exists $schema")
264+
} finally {
265+
conn.close()
266+
}
267+
}
243268
}

code/migration/src/main/scala/com/namely/chiefofstate/migration/versions/v6/V6.scala

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ import slick.basic.DatabaseConfig
1111
import slick.dbio.DBIO
1212
import slick.jdbc.JdbcProfile
1313

14+
/**
15+
* V6 migration
16+
*
17+
* @param journalJdbcConfig a db config
18+
* @param schema the COS schema name
19+
*/
1420
case class V6(journalJdbcConfig: DatabaseConfig[JdbcProfile]) extends Version {
1521
final val log: Logger = LoggerFactory.getLogger(getClass)
1622
override def versionNumber: Int = 6

code/migration/src/test/scala/com/namely/chiefofstate/migration/MigratorSpec.scala

Lines changed: 27 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import java.sql.DriverManager
2020
import scala.concurrent.Await
2121
import scala.concurrent.duration.Duration
2222
import scala.util.Success
23+
import com.namely.chiefofstate.migration.helper.DbHelper
2324

2425
class MigratorSpec extends BaseSpec with ForAllTestContainer {
2526

@@ -85,7 +86,7 @@ class MigratorSpec extends BaseSpec with ForAllTestContainer {
8586
".addVersion" should {
8687
"add to the versions queue in order" in {
8788
val dbConfig = getDbConfig()
88-
val migrator: Migrator = new Migrator(dbConfig)
89+
val migrator: Migrator = new Migrator(dbConfig, cosSchema)
8990

9091
// add versions out of order
9192
migrator.addVersion(getMockVersion(2))
@@ -105,7 +106,7 @@ class MigratorSpec extends BaseSpec with ForAllTestContainer {
105106
".getVersions" should {
106107
"filter versions" in {
107108
val dbConfig = getDbConfig()
108-
val migrator: Migrator = new Migrator(dbConfig)
109+
val migrator: Migrator = new Migrator(dbConfig, cosSchema)
109110

110111
// add versions
111112
migrator.addVersion(getMockVersion(1))
@@ -127,7 +128,7 @@ class MigratorSpec extends BaseSpec with ForAllTestContainer {
127128
".beforeAll" should {
128129
"create the versions table" in {
129130
val dbConfig = getDbConfig()
130-
val migrator = new Migrator(dbConfig)
131+
val migrator = new Migrator(dbConfig, cosSchema)
131132

132133
DbUtil.tableExists(dbConfig, Migrator.COS_MIGRATIONS_TABLE) shouldBe false
133134

@@ -140,7 +141,7 @@ class MigratorSpec extends BaseSpec with ForAllTestContainer {
140141
setEnv(Migrator.COS_MIGRATIONS_INITIAL_VERSION, "3")
141142

142143
val dbConfig = getDbConfig()
143-
val migrator = new Migrator(dbConfig)
144+
val migrator = new Migrator(dbConfig, cosSchema)
144145

145146
migrator.beforeAll().isSuccess shouldBe true
146147

@@ -165,7 +166,7 @@ class MigratorSpec extends BaseSpec with ForAllTestContainer {
165166
.once()
166167

167168
// define a migrator with two versions
168-
val migrator = new Migrator(dbConfig).addVersion(version1).addVersion(version2)
169+
val migrator = new Migrator(dbConfig, cosSchema).addVersion(version1).addVersion(version2)
169170

170171
val result = migrator.run()
171172

@@ -182,7 +183,7 @@ class MigratorSpec extends BaseSpec with ForAllTestContainer {
182183
Migrator.getCurrentVersionNumber(dbConfig) shouldBe Some(1)
183184

184185
// define a migrator
185-
val migrator = new Migrator(dbConfig)
186+
val migrator = new Migrator(dbConfig, cosSchema)
186187

187188
// define 3 dynamic versions
188189
(2 to 4).foreach(versionNumber => {
@@ -208,7 +209,10 @@ class MigratorSpec extends BaseSpec with ForAllTestContainer {
208209

209210
// define a migrator with versions that should not run (nothing mocked)
210211
val migrator =
211-
new Migrator(dbConfig).addVersion(getMockVersion(1)).addVersion(getMockVersion(2)).addVersion(getMockVersion(3))
212+
new Migrator(dbConfig, cosSchema)
213+
.addVersion(getMockVersion(1))
214+
.addVersion(getMockVersion(2))
215+
.addVersion(getMockVersion(3))
212216

213217
// set db version number to the highest version
214218
Migrator.createMigrationsTable(dbConfig)
@@ -430,4 +434,20 @@ class MigratorSpec extends BaseSpec with ForAllTestContainer {
430434
actual.failed.map(_.getMessage.endsWith("cannot be 'X'")) shouldBe Success(true)
431435
}
432436
}
437+
438+
".createSchema" should {
439+
"create the schema if not exists" in {
440+
// first, drop the schema
441+
DbHelper.dropSchema(container, cosSchema)
442+
// ensure schema not exists
443+
DbHelper.schemaExists(container, cosSchema) shouldBe false
444+
// construct db config
445+
val dbConfig: DatabaseConfig[JdbcProfile] =
446+
dbConfigFromUrl(container.jdbcUrl, container.username, container.password)
447+
// run create schema
448+
Migrator.createSchema(dbConfig, cosSchema)
449+
// ensure exists
450+
DbHelper.schemaExists(container, cosSchema) shouldBe true
451+
}
452+
}
433453
}

code/migration/src/test/scala/com/namely/chiefofstate/migration/helper/DbHelper.scala

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ package com.namely.chiefofstate.migration.helper
99
import com.namely.protobuf.chiefofstate.v1.persistence.{ EventWrapper, StateWrapper }
1010
import com.dimafeng.testcontainers.{ ForAllTestContainer, PostgreSQLContainer }
1111
import java.sql.{ Connection, DriverManager }
12+
import org.checkerframework.checker.units.qual.s
1213

1314
object DbHelper {
1415
val serializerId = 5001
@@ -88,4 +89,33 @@ object DbHelper {
8889
statement.executeBatch()
8990
conn.close()
9091
}
92+
93+
// drop the schema for creation testing
94+
def dropSchema(container: PostgreSQLContainer, schema: String): Unit = {
95+
val conn = getConnection(container)
96+
val statement = conn.createStatement()
97+
statement.addBatch(s"drop schema if exists $schema cascade")
98+
statement.executeBatch()
99+
conn.close()
100+
}
101+
102+
/**
103+
* returns true if schema exists (for testing)
104+
*
105+
* @param container test container for postgres
106+
* @param schema schema name
107+
* @return true if schema exists
108+
*/
109+
def schemaExists(container: PostgreSQLContainer, schema: String): Boolean = {
110+
val conn = getConnection(container)
111+
val statement = conn.createStatement()
112+
val sql = s"SELECT count(schema_name) FROM information_schema.schemata WHERE schema_name = '$schema';"
113+
val result = statement.executeQuery(sql)
114+
require(result.next, "broken resultset")
115+
val numSchemas = result.getInt(1)
116+
require(numSchemas < 2, "broken resultset")
117+
conn.close()
118+
// return true if found
119+
numSchemas == 1
120+
}
91121
}

code/service/src/main/scala/com/namely/chiefofstate/ServiceBootstrapper.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ object ServiceBootstrapper {
5858
TelemetryTools(cosConfig.telemetryConfig).start()
5959

6060
// We only proceed when the data stores and various migrations are done successfully.
61-
log.info("Journal and snapshot store created successfully. About to start...")
61+
log.info("Data store migration complete. About to start...")
6262

6363
val channel: ManagedChannel =
6464
NettyHelper

code/service/src/main/scala/com/namely/chiefofstate/ServiceMigrationRunner.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ object ServiceMigrationRunner {
6464
val priorOffsetStoreName: String =
6565
config.getString("chiefofstate.migration.v1.slick.offset-store.table")
6666

67+
val schema: String = config.getString("jdbc-default.schema")
68+
6769
val v1: V1 = V1(journalJdbcConfig, priorProjectionJdbcConfig, priorOffsetStoreName)
6870
val v2: V2 = V2(journalJdbcConfig, projectionJdbcConfig)(context.system)
6971
val v3: V3 = V3(journalJdbcConfig)
@@ -73,7 +75,7 @@ object ServiceMigrationRunner {
7375

7476
// instance of the migrator
7577
val migrator: Migrator =
76-
new Migrator(journalJdbcConfig)
78+
new Migrator(journalJdbcConfig, schema)
7779
.addVersion(v1)
7880
.addVersion(v2)
7981
.addVersion(v3)

0 commit comments

Comments
 (0)