diff --git a/.travis.yml b/.travis.yml index 8c29aff..b9b7149 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,9 +10,9 @@ matrix: - jdk: oraclejdk7 script: mvn test - jdk: oraclejdk8 - script: - - mvn test - - mvn verify + script: mvn test + - jdk: oraclejdk8 + script: mvn verify deploy: provider: script script: ./scripts/deploy.sh diff --git a/README.md b/README.md index 32169e8..c716962 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,7 @@ https://github.com/builtamont/cassandra-migration/releases ## Version 0.9 Release Pending Actions * Replace `config.Cluster.java` and `config.Keyspace.java` to the one provided by DataStax Cassandra driver - * Add additional features from upstream open PRs + * ~~Add additional features from upstream open PRs~~ (DONE as per 8 September 2016 PRs) * Add standalone Cassandra (DataStax Community Edition) integration test ## Non-Critical Pending Actions diff --git a/pom.xml b/pom.xml index bedc6ba..67126e2 100755 --- a/pom.xml +++ b/pom.xml @@ -197,9 +197,9 @@ 2.19.1 - 3 + 1 true - -Xmx1024m -XX:MaxPermSize=256m + -Xmx2048m -XX:MaxPermSize=512m diff --git a/src/main/java/com/builtamont/cassandra/migration/CassandraMigration.kt b/src/main/java/com/builtamont/cassandra/migration/CassandraMigration.kt index 6eca77d..3acaf08 100644 --- a/src/main/java/com/builtamont/cassandra/migration/CassandraMigration.kt +++ b/src/main/java/com/builtamont/cassandra/migration/CassandraMigration.kt @@ -38,7 +38,6 @@ import com.builtamont.cassandra.migration.internal.util.logging.LogFactory import com.datastax.driver.core.Cluster import com.datastax.driver.core.Metadata import com.datastax.driver.core.Session -import sun.reflect.generics.reflectiveObjects.NotImplementedException /** * This is the centre point of Cassandra migration, and for most users, the only class they will ever have to deal with. @@ -87,24 +86,18 @@ class CassandraMigration : CassandraMigrationConfiguration { * @return The number of successfully applied migrations. */ fun migrate(): Int { - return execute(object : Action { - override fun execute(session: Session): Int { - Initialize().run(session, keyspace, MigrationVersion.CURRENT.table) - - val migrationResolver = createMigrationResolver() - val schemaVersionDAO = SchemaVersionDAO(session, keyspace, MigrationVersion.CURRENT.table) - val migrate = Migrate( - migrationResolver, - configs.target, - schemaVersionDAO, - session, - keyspace.cluster.username, - configs.isAllowOutOfOrder - ) + return execute(migrateAction()) + } - return migrate.run() - } - }) + /** + * Starts the database migration. All pending migrations will be applied in order. + * Calling migrate on an up-to-date database has no effect. + * + * @param session The Cassandra connection session. + * @return The number of successfully applied migrations. + */ + fun migrate(session: Session): Int { + return execute(migrateAction(), session) } /** @@ -114,16 +107,18 @@ class CassandraMigration : CassandraMigrationConfiguration { * @return All migrations sorted by version, oldest first. */ fun info(): MigrationInfoService { - return execute(object : Action { - override fun execute(session: Session): MigrationInfoService { - val migrationResolver = createMigrationResolver() - val schemaVersionDAO = SchemaVersionDAO(session, keyspace, MigrationVersion.CURRENT.table) - val migrationInfoService = MigrationInfoServiceImpl(migrationResolver, schemaVersionDAO, configs.target, false, true) - migrationInfoService.refresh() + return execute(infoAction()) + } - return migrationInfoService - } - }) + /** + * Retrieves the complete information about all the migrations including applied, pending and current migrations with + * details and status. + * + * @param session The Cassandra connection session. + * @return All migrations sorted by version, oldest first. + */ + fun info(session: Session): MigrationInfoService { + return execute(infoAction(), session) } /** @@ -136,17 +131,29 @@ class CassandraMigration : CassandraMigrationConfiguration { * * versions have been resolved that haven't been applied yet */ fun validate() { - val validationError = execute(object : Action { - override fun execute(session: Session): String? { - val migrationResolver = createMigrationResolver() - val schemaVersionDAO = SchemaVersionDAO(session, keyspace, MigrationVersion.CURRENT.table) - val validate = Validate(migrationResolver, configs.target, schemaVersionDAO, true, false) - return validate.run() - } - }) + val validationError = execute(validateAction()) + + if (validationError != null) { + throw CassandraMigrationException("Validation failed. $validationError") + } + } + + /** + * Validate applied migrations against resolved ones (on the filesystem or classpath) + * to detect accidental changes that may prevent the schema(s) from being recreated exactly. + * + * Validation fails if: + * * differences in migration names, types or checksums are found + * * versions have been applied that aren't resolved locally anymore + * * versions have been resolved that haven't been applied yet + * + * @param session The Cassandra connection session. + */ + fun validate(session: Session) { + val validationError = execute(validateAction(), session) if (validationError != null) { - throw CassandraMigrationException("Validation failed. " + validationError) + throw CassandraMigrationException("Validation failed. $validationError") } } @@ -154,14 +161,16 @@ class CassandraMigration : CassandraMigrationConfiguration { * Baselines an existing database, excluding all migrations up to and including baselineVersion. */ fun baseline() { - execute(object : Action { - override fun execute(session: Session): Unit { - val migrationResolver = createMigrationResolver() - val schemaVersionDAO = SchemaVersionDAO(session, keyspace, MigrationVersion.CURRENT.table) - val baseline = Baseline(migrationResolver, baselineVersion, schemaVersionDAO, baselineDescription, keyspace.cluster.username) - baseline.run() - } - }) + execute(baselineAction()) + } + + /** + * Baselines an existing database, excluding all migrations up to and including baselineVersion. + * + * @param session The Cassandra connection session. + */ + fun baseline(session: Session) { + execute(baselineAction(), session) } /** @@ -179,16 +188,17 @@ class CassandraMigration : CassandraMigrationConfiguration { var cluster: Cluster? = null var session: Session? = null try { - if (null == keyspace) - throw IllegalArgumentException("Unable to establish Cassandra session. Keyspace is not configured.") - - if (null == keyspace.cluster) - throw IllegalArgumentException("Unable to establish Cassandra session. Cluster is not configured.") + // Guard clauses: Cluster and Keyspace must be defined + val errorMsg = "Unable to establish Cassandra session" + if (keyspace == null) throw IllegalArgumentException("$errorMsg. Keyspace is not configured.") + if (keyspace.cluster == null) throw IllegalArgumentException("$errorMsg. Cluster is not configured.") + if (keyspace.name.isNullOrEmpty()) throw IllegalArgumentException("$errorMsg. Keyspace is not specified.") + // Build the Cluster val builder = Cluster.Builder() builder.addContactPoints(*keyspace.cluster.contactpoints).withPort(keyspace.cluster.port) - if (null != keyspace.cluster.username && !keyspace.cluster.username.trim { it <= ' ' }.isEmpty()) { - if (null != keyspace.cluster.password && !keyspace.cluster.password.trim { it <= ' ' }.isEmpty()) { + if (!keyspace.cluster.username.isNullOrBlank()) { + if (!keyspace.cluster.password.isNullOrBlank()) { builder.withCredentials(keyspace.cluster.username, keyspace.cluster.password) } else { throw IllegalArgumentException("Password must be provided with username.") @@ -196,23 +206,19 @@ class CassandraMigration : CassandraMigrationConfiguration { } cluster = builder.build() - val metadata = cluster!!.metadata - LOG.info(getConnectionInfo(metadata)) + LOG.info(getConnectionInfo(cluster.metadata)) + // Create a new Session session = cluster.newSession() - if (null == keyspace.name || keyspace.name.trim { it <= ' ' }.length == 0) - throw IllegalArgumentException("Keyspace not specified.") - - val keyspaces = metadata.keyspaces - var keyspaceExists = false - for (keyspaceMetadata in keyspaces) { - if (keyspaceMetadata.name.equals(keyspace.name, ignoreCase = true)) - keyspaceExists = true + + // Connect to the specific Keyspace context (if already defined) + val keyspaces = cluster.metadata.keyspaces.map { it.name } + val keyspaceExists = keyspaces.first { it.equals(keyspace.name, ignoreCase = true) }.isNotEmpty() + if (keyspaceExists) { + session = cluster.connect(keyspace.name) + } else { + throw CassandraMigrationException("Keyspace: ${keyspace.name} does not exist.") } - if (keyspaceExists) - session!!.execute("USE " + keyspace.name) - else - throw CassandraMigrationException("Keyspace: " + keyspace.name + " does not exist.") result = action.execute(session) } finally { @@ -229,11 +235,22 @@ class CassandraMigration : CassandraMigrationConfiguration { } catch (e: Exception) { LOG.warn("Error closing Cassandra cluster") } - } return result } + /** + * Executes this command with an existing session, with proper resource handling and cleanup. + * + * @param action The action to execute. + * @param session The Cassandra connection session. + * @param T The action result type. + * @return The action result. + */ + internal fun execute(action: Action, session: Session): T { + return action.execute(session) + } + /** * Get Cassandra connection information. * @@ -263,6 +280,102 @@ class CassandraMigration : CassandraMigrationConfiguration { return CompositeMigrationResolver(classLoader, ScriptsLocations(*configs.scriptsLocations), configs.encoding) } + /** + * Creates the SchemaVersionDAO. + * + * @return A configured SchemaVersionDAO instance. + */ + private fun createSchemaVersionDAO(session: Session): SchemaVersionDAO { + return SchemaVersionDAO(session, keyspace, MigrationVersion.CURRENT.table) + } + + /** + * @return The database migration action. + */ + private fun migrateAction(): Action { + return object: Action { + override fun execute(session: Session): Int { + Initialize().run(session, keyspace, MigrationVersion.CURRENT.table) + + val migrationResolver = createMigrationResolver() + val schemaVersionDAO = createSchemaVersionDAO(session) + val migrate = Migrate( + migrationResolver, + configs.target, + schemaVersionDAO, + session, + keyspace.cluster.username, + configs.isAllowOutOfOrder + ) + + return migrate.run() + } + } + } + + /** + * @return The migration info service action. + */ + private fun infoAction(): Action { + return object : Action { + override fun execute(session: Session): MigrationInfoService { + val migrationResolver = createMigrationResolver() + val schemaVersionDAO = createSchemaVersionDAO(session) + val migrationInfoService = MigrationInfoServiceImpl( + migrationResolver, + schemaVersionDAO, + configs.target, + outOfOrder = false, + pendingOrFuture = true + ) + migrationInfoService.refresh() + + return migrationInfoService + } + } + } + + /** + * @return The migration validation action. + */ + private fun validateAction(): Action { + return object : Action { + override fun execute(session: Session): String? { + val migrationResolver = createMigrationResolver() + val schemaVersionDAO = createSchemaVersionDAO(session) + val validate = Validate( + migrationResolver, + configs.target, + schemaVersionDAO, + outOfOrder = true, + pendingOrFuture = false + ) + + return validate.run() + } + } + } + + /** + * @return The migration baselining action. + */ + private fun baselineAction(): Action { + return object : Action { + override fun execute(session: Session): Unit { + val migrationResolver = createMigrationResolver() + val schemaVersionDAO = createSchemaVersionDAO(session) + val baseline = Baseline( + migrationResolver, + baselineVersion, + schemaVersionDAO, + baselineDescription, + keyspace.cluster.username + ) + baseline.run() + } + } + } + /** * A Cassandra migration action that can be executed. * diff --git a/src/main/java/com/builtamont/cassandra/migration/internal/dbsupport/SchemaVersionDAO.java b/src/main/java/com/builtamont/cassandra/migration/internal/dbsupport/SchemaVersionDAO.java index be6ae8e..d12db20 100755 --- a/src/main/java/com/builtamont/cassandra/migration/internal/dbsupport/SchemaVersionDAO.java +++ b/src/main/java/com/builtamont/cassandra/migration/internal/dbsupport/SchemaVersionDAO.java @@ -382,7 +382,7 @@ private int calculateInstalledRank() { Select select = QueryBuilder .select("count") - .from(tableName + COUNTS_TABLE_NAME_SUFFIX); + .from(keyspace.getName(), tableName + COUNTS_TABLE_NAME_SUFFIX); select.where(eq("name", "installed_rank")); select.setConsistencyLevel(this.consistencyLevel); diff --git a/src/test/java/com/builtamont/cassandra/migration/BaseIT.java b/src/test/java/com/builtamont/cassandra/migration/BaseIT.java index 61f233d..8397637 100644 --- a/src/test/java/com/builtamont/cassandra/migration/BaseIT.java +++ b/src/test/java/com/builtamont/cassandra/migration/BaseIT.java @@ -1,3 +1,21 @@ +/** + * File : BaseIT.java + * License : + * Original - Copyright (c) 2015 - 2016 Contrast Security + * Derivative - Copyright (c) 2016 Citadel Technology Solutions Pte Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package com.builtamont.cassandra.migration; import com.builtamont.cassandra.migration.config.Keyspace; @@ -12,7 +30,8 @@ import org.junit.BeforeClass; public abstract class BaseIT { - public static final String CASSANDRA__KEYSPACE = "cassandra_migration_test"; + + public static final String CASSANDRA_KEYSPACE = "cassandra_migration_test"; public static final String CASSANDRA_CONTACT_POINT = "localhost"; public static final int CASSANDRA_PORT = 9147; public static final String CASSANDRA_USERNAME = "cassandra"; @@ -36,23 +55,26 @@ public static void afterSuite() { @Before public void createKeyspace() { Statement statement = new SimpleStatement( - "CREATE KEYSPACE " + CASSANDRA__KEYSPACE + + "CREATE KEYSPACE " + CASSANDRA_KEYSPACE + " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };" ); getSession(getKeyspace()).execute(statement); + + // Reconnect session to the keyspace + session = session.getCluster().connect(CASSANDRA_KEYSPACE); } @After public void dropKeyspace() { Statement statement = new SimpleStatement( - "DROP KEYSPACE " + CASSANDRA__KEYSPACE + ";" + "DROP KEYSPACE " + CASSANDRA_KEYSPACE + ";" ); getSession(getKeyspace()).execute(statement); } protected Keyspace getKeyspace() { Keyspace ks = new Keyspace(); - ks.setName(CASSANDRA__KEYSPACE); + ks.setName(CASSANDRA_KEYSPACE); ks.getCluster().setContactpoints(CASSANDRA_CONTACT_POINT); ks.getCluster().setPort(CASSANDRA_PORT); ks.getCluster().setUsername(CASSANDRA_USERNAME); @@ -60,13 +82,16 @@ protected Keyspace getKeyspace() { return ks; } - private Session getSession(Keyspace keyspace) { + protected Session getSession(Keyspace keyspace) { if (session != null && !session.isClosed()) return session; - com.datastax.driver.core.Cluster.Builder builder = new com.datastax.driver.core.Cluster.Builder(); - builder.addContactPoints(CASSANDRA_CONTACT_POINT).withPort(CASSANDRA_PORT); - builder.withCredentials(keyspace.getCluster().getUsername(), keyspace.getCluster().getPassword()); + String username = keyspace.getCluster().getUsername(); + String password = keyspace.getCluster().getPassword(); + Cluster.Builder builder = new Cluster.Builder(); + builder.addContactPoints(CASSANDRA_CONTACT_POINT) + .withPort(CASSANDRA_PORT) + .withCredentials(username, password); Cluster cluster = builder.build(); session = cluster.connect(); return session; diff --git a/src/test/java/com/builtamont/cassandra/migration/CassandraMigrationIT.java b/src/test/java/com/builtamont/cassandra/migration/CassandraMigrationIT.java index dfbc048..1cee1ba 100644 --- a/src/test/java/com/builtamont/cassandra/migration/CassandraMigrationIT.java +++ b/src/test/java/com/builtamont/cassandra/migration/CassandraMigrationIT.java @@ -1,5 +1,5 @@ /** - * File : CassandraMigrationIT.kt + * File : CassandraMigrationIT.java * License : * Original - Copyright (c) 2015 - 2016 Contrast Security * Derivative - Copyright (c) 2016 Citadel Technology Solutions Pte Ltd @@ -24,6 +24,7 @@ import com.builtamont.cassandra.migration.internal.metadatatable.AppliedMigration; import com.datastax.driver.core.ResultSet; import com.datastax.driver.core.Row; +import com.datastax.driver.core.Session; import com.datastax.driver.core.querybuilder.QueryBuilder; import com.datastax.driver.core.querybuilder.Select; import org.hamcrest.Matchers; @@ -40,243 +41,305 @@ public class CassandraMigrationIT extends BaseIT { - @Test - public void runApiTest() { - String[] scriptsLocations = { "migration/integ", "migration/integ/java" }; - CassandraMigration cm = new CassandraMigration(); - cm.getConfigs().setScriptsLocations(scriptsLocations); - cm.setKeyspace(getKeyspace()); - cm.migrate(); - - MigrationInfoService infoService = cm.info(); - System.out.println("Initial migration"); - System.out.println(MigrationInfoDumper.INSTANCE.dumpToAsciiTable(infoService.all())); - assertThat(infoService.all().length, is(4)); - for (MigrationInfo info : infoService.all()) { - assertThat(info.getVersion().getVersion(), anyOf(is("1.0.0"), is("2.0.0"), is("3.0"), is("3.0.1"))); - if (info.getVersion().equals("3.0.1")) { - assertThat(info.getDescription(), is("Three point zero one")); - assertThat(info.getType().name(), Matchers.is(MigrationType.JAVA_DRIVER.name())); - assertThat(info.getScript().contains(".java"), is(true)); - - Select select = QueryBuilder.select().column("value").from("test1"); - select.where(eq("space", "web")).and(eq("key", "facebook")); - ResultSet result = getSession().execute(select); - assertThat(result.one().getString("value"), is("facebook.com")); - } else if (info.getVersion().equals("3.0")) { - assertThat(info.getDescription(), is("Third")); - assertThat(info.getType().name(), Matchers.is(MigrationType.JAVA_DRIVER.name())); - assertThat(info.getScript().contains(".java"), is(true)); - - Select select = QueryBuilder.select().column("value").from("test1"); - select.where(eq("space", "web")).and(eq("key", "google")); - ResultSet result = getSession().execute(select); - assertThat(result.one().getString("value"), is("google.com")); - } else if (info.getVersion().equals("2.0.0")) { - assertThat(info.getDescription(), is("Second")); - assertThat(info.getType().name(), Matchers.is(MigrationType.CQL.name())); - assertThat(info.getScript().contains(".cql"), is(true)); - - Select select = QueryBuilder.select().column("title").column("message").from("contents"); - select.where(eq("id", 1)); - Row row = getSession().execute(select).one(); - assertThat(row.getString("title"), is("foo")); - assertThat(row.getString("message"), is("bar")); - } else if (info.getVersion().equals("1.0.0")) { - assertThat(info.getDescription(), is("First")); - assertThat(info.getType().name(), Matchers.is(MigrationType.CQL.name())); - assertThat(info.getScript().contains(".cql"), is(true)); - - Select select = QueryBuilder.select().column("value").from("test1"); - select.where(eq("space", "foo")).and(eq("key", "bar")); - ResultSet result = getSession().execute(select); - assertThat(result.one().getString("value"), is("profit!")); - } - - assertThat(info.getState().isApplied(), is(true)); - assertThat(info.getInstalledOn(), notNullValue()); - } - - // test out of order when out of order is not allowed - String[] outOfOrderScriptsLocations = { "migration/integ_outoforder", "migration/integ/java" }; - cm = new CassandraMigration(); - cm.getConfigs().setScriptsLocations(outOfOrderScriptsLocations); - cm.setKeyspace(getKeyspace()); - cm.migrate(); - - infoService = cm.info(); - System.out.println("Out of order migration with out-of-order ignored"); - System.out.println(MigrationInfoDumper.INSTANCE.dumpToAsciiTable(infoService.all())); - assertThat(infoService.all().length, is(5)); - for (MigrationInfo info : infoService.all()) { - assertThat(info.getVersion().getVersion(), - anyOf(is("1.0.0"), is("2.0.0"), is("3.0"), is("3.0.1"), is("1.1.1"))); - if (info.getVersion().equals("1.1.1")) { - assertThat(info.getDescription(), is("Late arrival")); - assertThat(info.getType().name(), Matchers.is(MigrationType.CQL.name())); - assertThat(info.getScript().contains(".cql"), is(true)); - assertThat(info.getState().isApplied(), is(false)); - assertThat(info.getInstalledOn(), nullValue()); - } - } - - // test out of order when out of order is allowed - String[] outOfOrder2ScriptsLocations = { "migration/integ_outoforder2", "migration/integ/java" }; - cm = new CassandraMigration(); - cm.getConfigs().setScriptsLocations(outOfOrder2ScriptsLocations); - cm.getConfigs().setAllowOutOfOrder(true); - cm.setKeyspace(getKeyspace()); - cm.migrate(); - - infoService = cm.info(); - System.out.println("Out of order migration with out-of-order allowed"); - System.out.println(MigrationInfoDumper.INSTANCE.dumpToAsciiTable(infoService.all())); - assertThat(infoService.all().length, is(6)); - for (MigrationInfo info : infoService.all()) { - assertThat(info.getVersion().getVersion(), - anyOf(is("1.0.0"), is("2.0.0"), is("3.0"), is("3.0.1"), is("1.1.1"), is("1.1.2"))); - if (info.getVersion().equals("1.1.2")) { - assertThat(info.getDescription(), is("Late arrival2")); - assertThat(info.getType().name(), Matchers.is(MigrationType.CQL.name())); - assertThat(info.getScript().contains(".cql"), is(true)); - assertThat(info.getState().isApplied(), is(true)); - assertThat(info.getInstalledOn(), notNullValue()); - } - } - - // test out of order when out of order is allowed again - String[] outOfOrder3ScriptsLocations = { "migration/integ_outoforder3", "migration/integ/java" }; - cm = new CassandraMigration(); - cm.getConfigs().setScriptsLocations(outOfOrder3ScriptsLocations); - cm.getConfigs().setAllowOutOfOrder(true); - cm.setKeyspace(getKeyspace()); - cm.migrate(); - - infoService = cm.info(); - System.out.println("Out of order migration with out-of-order allowed"); - System.out.println(MigrationInfoDumper.INSTANCE.dumpToAsciiTable(infoService.all())); - assertThat(infoService.all().length, is(7)); - for (MigrationInfo info : infoService.all()) { - assertThat(info.getVersion().getVersion(), - anyOf(is("1.0.0"), is("2.0.0"), is("3.0"), is("3.0.1"), is("1.1.1"), is("1.1.2"), is("1.1.3"))); - if (info.getVersion().equals("1.1.3")) { - assertThat(info.getDescription(), is("Late arrival3")); - assertThat(info.getType().name(), Matchers.is(MigrationType.CQL.name())); - assertThat(info.getScript().contains(".cql"), is(true)); - assertThat(info.getState().isApplied(), is(true)); - assertThat(info.getInstalledOn(), notNullValue()); - } - } - } - - @Test - public void testValidate() { - // apply migration scripts - String[] scriptsLocations = { "migration/integ", "migration/integ/java" }; - CassandraMigration cm = new CassandraMigration(); - cm.getConfigs().setScriptsLocations(scriptsLocations); - cm.setKeyspace(getKeyspace()); - cm.migrate(); - - MigrationInfoService infoService = cm.info(); - String validationError = infoService.validate(); - Assert.assertNull(validationError); - - cm = new CassandraMigration(); - cm.getConfigs().setScriptsLocations(scriptsLocations); - cm.setKeyspace(getKeyspace()); - - cm.validate(); - - cm = new CassandraMigration(); - cm.getConfigs().setScriptsLocations(new String[] { "migration/integ/java" }); - cm.setKeyspace(getKeyspace()); - - try { - cm.validate(); - Assert.fail("The expected CassandraMigrationException was not raised"); - } catch (CassandraMigrationException e) { - Assert.assertTrue("expected CassandraMigrationException", true); - } - } - - static boolean runCmdTestCompleted = false; - static boolean runCmdTestSuccess = false; - - @Test - public void runCmdTest() throws IOException, InterruptedException { - String shell = "java -jar" - + " -Dcassandra.migration.scripts.locations=filesystem:target/test-classes/migration/integ" - + " -Dcassandra.migration.cluster.contactpoints=" + CASSANDRA_CONTACT_POINT - + " -Dcassandra.migration.cluster.port=" + CASSANDRA_PORT - + " -Dcassandra.migration.cluster.username=" + CASSANDRA_USERNAME - + " -Dcassandra.migration.cluster.password=" + CASSANDRA_PASSWORD - + " -Dcassandra.migration.keyspace.name=" + CASSANDRA__KEYSPACE - + " target/*-jar-with-dependencies.jar" + " migrate"; - ProcessBuilder builder; - if (isWindows()) { - throw new IllegalStateException(); - } else { - builder = new ProcessBuilder("bash", "-c", shell); - } - builder.redirectErrorStream(true); - final Process process = builder.start(); - - watch(process); - - while (!runCmdTestCompleted) - Thread.sleep(1000L); - - assertThat(runCmdTestSuccess, is(true)); - } - - @Test - public void testBaseLine(){ - String[] scriptsLocations = {"migration/integ", "migration/integ/java"}; - CassandraMigration cm = new CassandraMigration(); - cm.getConfigs().setScriptsLocations(scriptsLocations); - cm.setKeyspace(getKeyspace()); - cm.baseline(); - - SchemaVersionDAO schemaVersionDAO = new SchemaVersionDAO(getSession(), getKeyspace(), MigrationVersion.Companion.getCURRENT().getTable()); - AppliedMigration baselineMarker = schemaVersionDAO.getBaselineMarker(); - assertThat(baselineMarker.getVersion(), is(MigrationVersion.Companion.fromVersion("1"))); - } - - @Test(expected = CassandraMigrationException.class) - public void testBaseLineWithMigrations() { - String[] scriptsLocations = { "migration/integ", "migration/integ/java" }; - CassandraMigration cm = new CassandraMigration(); - cm.getConfigs().setScriptsLocations(scriptsLocations); - cm.setKeyspace(getKeyspace()); - cm.migrate(); - - cm = new CassandraMigration(); - cm.getConfigs().setScriptsLocations(scriptsLocations); - cm.setKeyspace(getKeyspace()); - cm.baseline(); - } - - private static void watch(final Process process) { - new Thread(new Runnable() { - public void run() { - BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream())); - String line; - try { - while ((line = input.readLine()) != null) { - if (line.contains("Successfully applied 2 migration(s)")) - runCmdTestSuccess = true; - System.out.println(line); - } - } catch (IOException e) { - e.printStackTrace(); - } - runCmdTestCompleted = true; - } - }).start(); - } - - private boolean isWindows() { - return (System.getProperty("os.name").toLowerCase()).contains("win"); - } + @Test + public void runApiTest() { + String[] scriptsLocations = { "migration/integ", "migration/integ/java" }; + CassandraMigration cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(scriptsLocations); + cm.setKeyspace(getKeyspace()); + cm.migrate(); + + MigrationInfoService infoService = cm.info(); + System.out.println("Initial migration"); + System.out.println(MigrationInfoDumper.INSTANCE.dumpToAsciiTable(infoService.all())); + assertThat(infoService.all().length, is(4)); + for (MigrationInfo info : infoService.all()) { + assertThat(info.getVersion().getVersion(), anyOf(is("1.0.0"), is("2.0.0"), is("3.0"), is("3.0.1"))); + if (info.getVersion().equals("3.0.1")) { + assertThat(info.getDescription(), is("Three point zero one")); + assertThat(info.getType().name(), Matchers.is(MigrationType.JAVA_DRIVER.name())); + assertThat(info.getScript().contains(".java"), is(true)); + + Select select = QueryBuilder.select().column("value").from("test1"); + select.where(eq("space", "web")).and(eq("key", "facebook")); + ResultSet result = getSession().execute(select); + assertThat(result.one().getString("value"), is("facebook.com")); + } else if (info.getVersion().equals("3.0")) { + assertThat(info.getDescription(), is("Third")); + assertThat(info.getType().name(), Matchers.is(MigrationType.JAVA_DRIVER.name())); + assertThat(info.getScript().contains(".java"), is(true)); + + Select select = QueryBuilder.select().column("value").from("test1"); + select.where(eq("space", "web")).and(eq("key", "google")); + ResultSet result = getSession().execute(select); + assertThat(result.one().getString("value"), is("google.com")); + } else if (info.getVersion().equals("2.0.0")) { + assertThat(info.getDescription(), is("Second")); + assertThat(info.getType().name(), Matchers.is(MigrationType.CQL.name())); + assertThat(info.getScript().contains(".cql"), is(true)); + + Select select = QueryBuilder.select().column("title").column("message").from("contents"); + select.where(eq("id", 1)); + Row row = getSession().execute(select).one(); + assertThat(row.getString("title"), is("foo")); + assertThat(row.getString("message"), is("bar")); + } else if (info.getVersion().equals("1.0.0")) { + assertThat(info.getDescription(), is("First")); + assertThat(info.getType().name(), Matchers.is(MigrationType.CQL.name())); + assertThat(info.getScript().contains(".cql"), is(true)); + + Select select = QueryBuilder.select().column("value").from("test1"); + select.where(eq("space", "foo")).and(eq("key", "bar")); + ResultSet result = getSession().execute(select); + assertThat(result.one().getString("value"), is("profit!")); + } + + assertThat(info.getState().isApplied(), is(true)); + assertThat(info.getInstalledOn(), notNullValue()); + } + + // test out of order when out of order is not allowed + String[] outOfOrderScriptsLocations = { "migration/integ_outoforder", "migration/integ/java" }; + cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(outOfOrderScriptsLocations); + cm.setKeyspace(getKeyspace()); + cm.migrate(); + + infoService = cm.info(); + System.out.println("Out of order migration with out-of-order ignored"); + System.out.println(MigrationInfoDumper.INSTANCE.dumpToAsciiTable(infoService.all())); + assertThat(infoService.all().length, is(5)); + for (MigrationInfo info : infoService.all()) { + assertThat(info.getVersion().getVersion(), + anyOf(is("1.0.0"), is("2.0.0"), is("3.0"), is("3.0.1"), is("1.1.1"))); + if (info.getVersion().equals("1.1.1")) { + assertThat(info.getDescription(), is("Late arrival")); + assertThat(info.getType().name(), Matchers.is(MigrationType.CQL.name())); + assertThat(info.getScript().contains(".cql"), is(true)); + assertThat(info.getState().isApplied(), is(false)); + assertThat(info.getInstalledOn(), nullValue()); + } + } + + // test out of order when out of order is allowed + String[] outOfOrder2ScriptsLocations = { "migration/integ_outoforder2", "migration/integ/java" }; + cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(outOfOrder2ScriptsLocations); + cm.getConfigs().setAllowOutOfOrder(true); + cm.setKeyspace(getKeyspace()); + cm.migrate(); + + infoService = cm.info(); + System.out.println("Out of order migration with out-of-order allowed"); + System.out.println(MigrationInfoDumper.INSTANCE.dumpToAsciiTable(infoService.all())); + assertThat(infoService.all().length, is(6)); + for (MigrationInfo info : infoService.all()) { + assertThat(info.getVersion().getVersion(), + anyOf(is("1.0.0"), is("2.0.0"), is("3.0"), is("3.0.1"), is("1.1.1"), is("1.1.2"))); + if (info.getVersion().equals("1.1.2")) { + assertThat(info.getDescription(), is("Late arrival2")); + assertThat(info.getType().name(), Matchers.is(MigrationType.CQL.name())); + assertThat(info.getScript().contains(".cql"), is(true)); + assertThat(info.getState().isApplied(), is(true)); + assertThat(info.getInstalledOn(), notNullValue()); + } + } + + // test out of order when out of order is allowed again + String[] outOfOrder3ScriptsLocations = { "migration/integ_outoforder3", "migration/integ/java" }; + cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(outOfOrder3ScriptsLocations); + cm.getConfigs().setAllowOutOfOrder(true); + cm.setKeyspace(getKeyspace()); + cm.migrate(); + + infoService = cm.info(); + System.out.println("Out of order migration with out-of-order allowed"); + System.out.println(MigrationInfoDumper.INSTANCE.dumpToAsciiTable(infoService.all())); + assertThat(infoService.all().length, is(7)); + for (MigrationInfo info : infoService.all()) { + assertThat(info.getVersion().getVersion(), + anyOf(is("1.0.0"), is("2.0.0"), is("3.0"), is("3.0.1"), is("1.1.1"), is("1.1.2"), is("1.1.3"))); + if (info.getVersion().equals("1.1.3")) { + assertThat(info.getDescription(), is("Late arrival3")); + assertThat(info.getType().name(), Matchers.is(MigrationType.CQL.name())); + assertThat(info.getScript().contains(".cql"), is(true)); + assertThat(info.getState().isApplied(), is(true)); + assertThat(info.getInstalledOn(), notNullValue()); + } + } + } + + @Test + public void testValidate() { + // apply migration scripts + String[] scriptsLocations = { "migration/integ", "migration/integ/java" }; + CassandraMigration cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(scriptsLocations); + cm.setKeyspace(getKeyspace()); + cm.migrate(); + + MigrationInfoService infoService = cm.info(); + String validationError = infoService.validate(); + Assert.assertNull(validationError); + + cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(scriptsLocations); + cm.setKeyspace(getKeyspace()); + + cm.validate(); + + cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(new String[] { "migration/integ/java" }); + cm.setKeyspace(getKeyspace()); + + try { + cm.validate(); + Assert.fail("The expected CassandraMigrationException was not raised"); + } catch (CassandraMigrationException e) { + Assert.assertTrue("expected CassandraMigrationException", true); + } + } + + @Test + public void testValidateWithSession() { + // apply migration scripts + String[] scriptsLocations = { "migration/integ", "migration/integ/java" }; + Session session = getSession(); + CassandraMigration cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(scriptsLocations); + cm.setKeyspace(getKeyspace()); + cm.migrate(session); + + MigrationInfoService infoService = cm.info(session); + String validationError = infoService.validate(); + Assert.assertNull(validationError); + + cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(scriptsLocations); + cm.setKeyspace(getKeyspace()); + + cm.validate(session); + + cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(new String[] { "migration/integ/java" }); + cm.setKeyspace(getKeyspace()); + + try { + cm.validate(session); + Assert.fail("The expected CassandraMigrationException was not raised"); + } catch (CassandraMigrationException e) { + } + + Assert.assertFalse(session.isClosed()); + } + + @Test + public void testBaseLine() { + String[] scriptsLocations = {"migration/integ", "migration/integ/java"}; + CassandraMigration cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(scriptsLocations); + cm.setKeyspace(getKeyspace()); + cm.baseline(); + + SchemaVersionDAO schemaVersionDAO = new SchemaVersionDAO(getSession(), getKeyspace(), MigrationVersion.Companion.getCURRENT().getTable()); + AppliedMigration baselineMarker = schemaVersionDAO.getBaselineMarker(); + assertThat(baselineMarker.getVersion(), is(MigrationVersion.Companion.fromVersion("1"))); + } + + @Test + public void testBaseLineWithSession() { + String[] scriptsLocations = {"migration/integ", "migration/integ/java"}; + Session session = getSession(); + CassandraMigration cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(scriptsLocations); + cm.setKeyspace(getKeyspace()); + cm.baseline(session); + + SchemaVersionDAO schemaVersionDAO = new SchemaVersionDAO(getSession(), getKeyspace(), MigrationVersion.Companion.getCURRENT().getTable()); + AppliedMigration baselineMarker = schemaVersionDAO.getBaselineMarker(); + assertThat(baselineMarker.getVersion(), is(MigrationVersion.Companion.fromVersion("1"))); + } + + @Test(expected = CassandraMigrationException.class) + public void testBaseLineWithMigrations() { + String[] scriptsLocations = { "migration/integ", "migration/integ/java" }; + CassandraMigration cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(scriptsLocations); + cm.setKeyspace(getKeyspace()); + cm.migrate(); + + cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(scriptsLocations); + cm.setKeyspace(getKeyspace()); + cm.baseline(); + } + + @Test(expected = CassandraMigrationException.class) + public void testBaseLineWithMigrationsWithSession() { + String[] scriptsLocations = { "migration/integ", "migration/integ/java" }; + Session session = getSession(); + CassandraMigration cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(scriptsLocations); + cm.setKeyspace(getKeyspace()); + cm.migrate(session); + + cm = new CassandraMigration(); + cm.getConfigs().setScriptsLocations(scriptsLocations); + cm.setKeyspace(getKeyspace()); + cm.baseline(session); + } + + static boolean runCmdTestCompleted = false; + static boolean runCmdTestSuccess = false; + + @Test + public void runCmdTest() throws IOException, InterruptedException { + String shell = "java -jar" + + " -Dcassandra.migration.scripts.locations=filesystem:target/test-classes/migration/integ" + + " -Dcassandra.migration.cluster.contactpoints=" + CASSANDRA_CONTACT_POINT + + " -Dcassandra.migration.cluster.port=" + CASSANDRA_PORT + + " -Dcassandra.migration.cluster.username=" + CASSANDRA_USERNAME + + " -Dcassandra.migration.cluster.password=" + CASSANDRA_PASSWORD + + " -Dcassandra.migration.keyspace.name=" + CASSANDRA_KEYSPACE + + " target/*-jar-with-dependencies.jar" + " migrate"; + ProcessBuilder builder; + if (isWindows()) { + throw new IllegalStateException(); + } else { + builder = new ProcessBuilder("bash", "-c", shell); + } + builder.redirectErrorStream(true); + final Process process = builder.start(); + + watch(process); + + while (!runCmdTestCompleted) + Thread.sleep(1000L); + + assertThat(runCmdTestSuccess, is(true)); + } + + private static void watch(final Process process) { + new Thread(new Runnable() { + public void run() { + BufferedReader input = new BufferedReader(new InputStreamReader(process.getInputStream())); + String line; + try { + while ((line = input.readLine()) != null) { + if (line.contains("Successfully applied 2 migration(s)")) + runCmdTestSuccess = true; + System.out.println(line); + } + } catch (IOException e) { + e.printStackTrace(); + } + runCmdTestCompleted = true; + } + }).start(); + } + + private boolean isWindows() { + return (System.getProperty("os.name").toLowerCase()).contains("win"); + } }