/**
* 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 org.apache.aurora.scheduler.storage.db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.List;
import java.util.Optional;
import com.google.common.collect.ImmutableList;
import com.google.common.io.CharStreams;
import com.google.inject.Injector;
import org.apache.aurora.scheduler.storage.db.views.MigrationChangelogEntry;
import org.apache.ibatis.migration.Change;
import org.apache.ibatis.migration.JavaMigrationLoader;
import org.apache.ibatis.migration.MigrationLoader;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.junit.Test;
import static org.apache.aurora.scheduler.storage.db.DbModule.testModuleWithWorkQueue;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class MigrationManagerImplIT {
private Injector createMigrationInjector(MigrationLoader migrationLoader) {
return DbUtil.createStorageInjector(
testModuleWithWorkQueue(),
new DbModule.MigrationManagerModule(migrationLoader));
}
/**
* Ensure all changes have been applied and their downgrade scripts stored appropriately.
*
* @param sqlSessionFactory The sql session factory.
* @param loader A migration loader.
* @throws Exception If the changes were not applied properly.
*/
private void assertMigrationComplete(
SqlSessionFactory sqlSessionFactory,
MigrationLoader loader) throws Exception {
try (SqlSession session = sqlSessionFactory.openSession()) {
MigrationMapper mapper = session.getMapper(MigrationMapper.class);
List<MigrationChangelogEntry> appliedChanges = mapper.selectAll();
for (Change change : loader.getMigrations()) {
Optional<MigrationChangelogEntry> appliedChange = appliedChanges
.stream()
.filter(c -> c.getId().equals(change.getId()))
.findFirst();
assertTrue(appliedChange.isPresent());
assertEquals(
CharStreams.toString(loader.getScriptReader(change, true /* undo */)),
appliedChange.get().getDowngradeScript());
}
// As long as the tables exist, neither of these statements should fail.
try (Connection c = session.getConnection()) {
try (PreparedStatement select = c.prepareStatement("SELECT * FROM V001_test_table")) {
select.execute();
}
try (PreparedStatement select = c.prepareStatement("SELECT * FROM V002_test_table2")) {
select.execute();
}
}
}
}
@Test
public void testMigrate() throws Exception {
MigrationLoader loader = new JavaMigrationLoader(
"org.apache.aurora.scheduler.storage.db.testmigration");
Injector injector = createMigrationInjector(loader);
injector.getInstance(MigrationManager.class).migrate();
assertMigrationComplete(injector.getInstance(SqlSessionFactory.class), loader);
}
@Test
public void testNoMigrationNecessary() throws Exception {
MigrationLoader loader = new JavaMigrationLoader(
"org.apache.aurora.scheduler.storage.db.testmigration");
Injector injector = createMigrationInjector(loader);
MigrationManager migrationManager = injector.getInstance(MigrationManager.class);
migrationManager.migrate();
SqlSessionFactory sqlSessionFactory = injector.getInstance(SqlSessionFactory.class);
assertMigrationComplete(sqlSessionFactory, loader);
// Run the migration a second time, no changes should be made.
migrationManager.migrate();
assertMigrationComplete(sqlSessionFactory, loader);
}
private void assertRollbackComplete(SqlSessionFactory sqlSessionFactory) throws Exception {
try (SqlSession session = sqlSessionFactory.openSession()) {
MigrationMapper mapper = session.getMapper(MigrationMapper.class);
assertTrue(mapper.selectAll().isEmpty());
try (Connection c = session.getConnection()) {
for (String table : ImmutableList.of("V001_test_table", "V002_test_table2")) {
try (PreparedStatement select = c.prepareStatement("SELECT * FROM " + table)) {
select.execute();
fail("Select from " + table + " should have failed, the table should have been "
+ "dropped.");
} catch (SQLException e) {
// This exception is expected.
assertTrue(
e.getMessage().startsWith("Table \"" + table.toUpperCase() + "\" not found"));
}
}
}
}
}
@Test
public void testRollback() throws Exception {
// Run a normal migration which will apply one the change found in the testmigration package.
MigrationLoader loader = new JavaMigrationLoader(
"org.apache.aurora.scheduler.storage.db.testmigration");
Injector injector = createMigrationInjector(loader);
MigrationManager migrationManager = injector.getInstance(MigrationManager.class);
migrationManager.migrate();
// Now we intentionally pass a reference to a non-existent package to ensure that no migrations
// are found. As such a rollback is expected to be detected and a downgrade be performed.
MigrationLoader rollbackLoader = new JavaMigrationLoader(
"org.apache.aurora.scheduler.storage.db.nomigrations");
Injector rollbackInjector = createMigrationInjector(rollbackLoader);
MigrationManager rollbackManager = rollbackInjector.getInstance(MigrationManager.class);
rollbackManager.migrate();
assertRollbackComplete(rollbackInjector.getInstance(SqlSessionFactory.class));
}
}