package com.contrastsecurity.cassandra.migration.action;
import com.contrastsecurity.cassandra.migration.CassandraMigrationException;
import com.contrastsecurity.cassandra.migration.dao.SchemaVersionDAO;
import com.contrastsecurity.cassandra.migration.info.*;
import com.contrastsecurity.cassandra.migration.logging.Log;
import com.contrastsecurity.cassandra.migration.logging.LogFactory;
import com.contrastsecurity.cassandra.migration.resolver.MigrationExecutor;
import com.contrastsecurity.cassandra.migration.resolver.MigrationResolver;
import com.contrastsecurity.cassandra.migration.utils.StopWatch;
import com.contrastsecurity.cassandra.migration.utils.TimeFormat;
import com.datastax.driver.core.Session;
public class Migrate {
private static final Log LOG = LogFactory.getLog(Migrate.class);
private final MigrationVersion target;
private final SchemaVersionDAO schemaVersionDAO;
private final MigrationResolver migrationResolver;
private final Session session;
private final String user;
private final boolean allowOutOfOrder;
public Migrate(MigrationResolver migrationResolver, MigrationVersion target, SchemaVersionDAO schemaVersionDAO,
Session session, String user, boolean allowOutOfOrder) {
this.migrationResolver = migrationResolver;
this.schemaVersionDAO = schemaVersionDAO;
this.session = session;
this.target = target;
this.user = user;
this.allowOutOfOrder = allowOutOfOrder;
}
public int run() {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
int migrationSuccessCount = 0;
while (true) {
final boolean firstRun = migrationSuccessCount == 0;
MigrationInfoService infoService = new MigrationInfoService(migrationResolver, schemaVersionDAO, target, allowOutOfOrder, true);
infoService.refresh();
MigrationVersion currentSchemaVersion = MigrationVersion.EMPTY;
if (infoService.current() != null) {
currentSchemaVersion = infoService.current().getVersion();
}
if (firstRun) {
LOG.info("Current version of keyspace " + schemaVersionDAO.getKeyspace().getName() + ": " + currentSchemaVersion);
}
MigrationInfo[] future = infoService.future();
if (future.length > 0) {
MigrationInfo[] resolved = infoService.resolved();
if (resolved.length == 0) {
LOG.warn("Keyspace " + schemaVersionDAO.getKeyspace().getName() + " has version " + currentSchemaVersion
+ ", but no migration could be resolved in the configured locations !");
} else {
LOG.warn("Keyspace " + schemaVersionDAO.getKeyspace().getName() + " has a version (" + currentSchemaVersion
+ ") that is newer than the latest available migration ("
+ resolved[resolved.length - 1].getVersion() + ") !");
}
}
MigrationInfo[] failed = infoService.failed();
if (failed.length > 0) {
if ((failed.length == 1)
&& (failed[0].getState() == MigrationState.FUTURE_FAILED)) {
LOG.warn("Keyspace " + schemaVersionDAO.getKeyspace().getName() + " contains a failed future migration to version " + failed[0].getVersion() + " !");
} else {
throw new CassandraMigrationException("Keyspace " + schemaVersionDAO.getKeyspace().getName() + " contains a failed migration to version " + failed[0].getVersion() + " !");
}
}
MigrationInfo[] pendingMigrations = infoService.pending();
if (pendingMigrations.length == 0) {
break;
}
boolean isOutOfOrder = pendingMigrations[0].getVersion().compareTo(currentSchemaVersion) < 0;
MigrationVersion mv = applyMigration(pendingMigrations[0], isOutOfOrder);
if(mv == null) {
//no more migrations
break;
}
migrationSuccessCount++;
}
stopWatch.stop();
logSummary(migrationSuccessCount, stopWatch.getTotalTimeMillis());
return migrationSuccessCount;
}
private MigrationVersion applyMigration(final MigrationInfo migration, boolean isOutOfOrder) {
MigrationVersion version = migration.getVersion();
LOG.info("Migrating keyspace " + schemaVersionDAO.getKeyspace().getName() + " to version " + version + " - " + migration.getDescription() +
(isOutOfOrder ? " (out of order)" : ""));
StopWatch stopWatch = new StopWatch();
stopWatch.start();
try {
final MigrationExecutor migrationExecutor = migration.getResolvedMigration().getExecutor();
try {
migrationExecutor.execute(session);
} catch (Exception e) {
throw new CassandraMigrationException("Unable to apply migration", e);
}
LOG.debug("Successfully completed and committed migration of keyspace " +
schemaVersionDAO.getKeyspace().getName() + " to version " + version);
} catch (CassandraMigrationException e) {
String failedMsg = "Migration of keyspace " + schemaVersionDAO.getKeyspace().getName() +
" to version " + version + " failed!";
LOG.error(failedMsg + " Please restore backups and roll back database and code!");
stopWatch.stop();
int executionTime = (int) stopWatch.getTotalTimeMillis();
AppliedMigration appliedMigration = new AppliedMigration(version, migration.getDescription(),
migration.getType(), migration.getScript(), migration.getChecksum(), user, executionTime, false);
schemaVersionDAO.addAppliedMigration(appliedMigration);
throw e;
}
stopWatch.stop();
int executionTime = (int) stopWatch.getTotalTimeMillis();
AppliedMigration appliedMigration = new AppliedMigration(version, migration.getDescription(),
migration.getType(), migration.getScript(), migration.getChecksum(), user, executionTime, true);
schemaVersionDAO.addAppliedMigration(appliedMigration);
return version;
}
/**
* Logs the summary of this migration run.
*
* @param migrationSuccessCount The number of successfully applied migrations.
* @param executionTime The total time taken to perform this migration run (in ms).
*/
private void logSummary(int migrationSuccessCount, long executionTime) {
if (migrationSuccessCount == 0) {
LOG.info("Keyspace " + schemaVersionDAO.getKeyspace().getName() + " is up to date. No migration necessary.");
return;
}
if (migrationSuccessCount == 1) {
LOG.info("Successfully applied 1 migration to keyspace " + schemaVersionDAO.getKeyspace().getName() + " (execution time " + TimeFormat.format(executionTime) + ").");
} else {
LOG.info("Successfully applied " + migrationSuccessCount + " migrations to keyspace " + schemaVersionDAO.getKeyspace().getName() + " (execution time " + TimeFormat.format(executionTime) + ").");
}
}
}