From df9e35b688819f89eceec22c0f64fda0fb7c32e5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Tue, 13 Dec 2016 16:43:47 +0100 Subject: [PATCH] SONAR-8445 do auto upgrade on fresh install without Ruby --- .../db/migration/AutoDbMigration.java} | 74 ++++----- .../db/migration/AutoDbMigrationTest.java | 144 ++++++++++++++++++ .../org/sonar/server/platform/Platform.java | 18 +-- .../db/migration/DatabaseMigrationImpl.java | 24 ++- .../platform/db/migrations/package-info.java | 23 --- .../platformlevel/PlatformLevel2.java | 5 +- .../platformlevel/PlatformLevelSafeMode.java | 5 +- .../ruby/CallDatabaseVersionUpgrade.java | 33 ---- .../sonar/server/ruby/PlatformRubyBridge.java | 10 -- .../org/sonar/server/ruby/RubyBridge.java | 6 - .../server/ruby/RubyDatabaseMigration.java | 32 ---- .../ruby/call_databaseversion_upgrade.rb | 12 -- .../ruby/call_invalidate_metric_cache.rb | 3 + .../ruby/call_load_java_web_services.rb | 9 +- ...baseMigrationImplConcurrentAccessTest.java | 41 +++-- .../migration/DatabaseMigrationImplTest.java | 27 ++-- .../db/migrations/DatabaseMigratorTest.java | 83 ---------- .../server/ruby/PlatformRubyBridgeTest.java | 13 -- .../org/sonar/server/ruby/database_version.rb | 8 +- .../main/webapp/WEB-INF/config/environment.rb | 40 +++-- .../webapp/WEB-INF/lib/database_version.rb | 9 -- 21 files changed, 273 insertions(+), 346 deletions(-) rename server/{sonar-server/src/main/java/org/sonar/server/platform/db/migrations/DatabaseMigrator.java => sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/AutoDbMigration.java} (52%) create mode 100644 server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/AutoDbMigrationTest.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/package-info.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/ruby/CallDatabaseVersionUpgrade.java delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/ruby/RubyDatabaseMigration.java delete mode 100644 server/sonar-server/src/main/resources/org/sonar/server/ruby/call_databaseversion_upgrade.rb delete mode 100644 server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/DatabaseMigratorTest.java diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/DatabaseMigrator.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/AutoDbMigration.java similarity index 52% rename from server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/DatabaseMigrator.java rename to server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/AutoDbMigration.java index 1746941c190..617d1a4fedb 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/DatabaseMigrator.java +++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/AutoDbMigration.java @@ -17,7 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.platform.db.migrations; +package org.sonar.server.platform.db.migration; import com.google.common.annotations.VisibleForTesting; import java.sql.Connection; @@ -25,68 +25,60 @@ import org.apache.commons.dbutils.DbUtils; import org.apache.ibatis.session.SqlSession; import org.picocontainer.Startable; import org.sonar.api.platform.ServerUpgradeStatus; -import org.sonar.api.server.ServerSide; import org.sonar.api.utils.log.Loggers; import org.sonar.db.DbClient; import org.sonar.db.DdlUtils; -import org.sonar.server.plugins.ServerPluginRepository; +import org.sonar.db.dialect.Dialect; +import org.sonar.db.dialect.H2; +import org.sonar.server.platform.db.migration.engine.MigrationEngine; /** - * Restore schema by executing DDL scripts. Only H2 database is supported. - * Other databases are created by Ruby on Rails migrations. - * - * @since 2.12 + * FIXME fix this class to remove use of DdlUtils.createSchema */ -@ServerSide -public class DatabaseMigrator implements Startable { - - private final DbClient dbClient; +public class AutoDbMigration implements Startable { private final ServerUpgradeStatus serverUpgradeStatus; + private final DbClient dbClient; + private final MigrationEngine migrationEngine; - /** - * ServerPluginRepository is used to ensure H2 schema creation is done only after copy of bundle plugins have been done - */ - public DatabaseMigrator(DbClient dbClient, ServerUpgradeStatus serverUpgradeStatus, ServerPluginRepository unused) { - this.dbClient = dbClient; + public AutoDbMigration(ServerUpgradeStatus serverUpgradeStatus, DbClient dbClient, MigrationEngine migrationEngine) { this.serverUpgradeStatus = serverUpgradeStatus; + this.dbClient = dbClient; + this.migrationEngine = migrationEngine; } @Override public void start() { - createDatabase(); - } + if (!serverUpgradeStatus.isFreshInstall()) { + return; + } - @Override - public void stop() { - // Nothing to do + Loggers.get(getClass()).info("Automatically perform DB migration on fresh install"); + Dialect dialect = dbClient.getDatabase().getDialect(); + if (H2.ID.equals(dialect.getId())) { + installH2(); + } else { + migrationEngine.execute(); + } } - /** - * @return true if the database has been created, false if this database is not supported or if database has already been created - */ @VisibleForTesting - boolean createDatabase() { - if (DdlUtils.supportsDialect(dbClient.getDatabase().getDialect().getId()) && serverUpgradeStatus.isFreshInstall()) { - Loggers.get(getClass()).info("Create database"); - SqlSession session = dbClient.openSession(false); - Connection connection = null; - try { - connection = session.getConnection(); - createSchema(connection, dbClient.getDatabase().getDialect().getId()); - return true; - } finally { - session.close(); - - // The connection is probably already closed by session.close() - // but it's not documented in mybatis javadoc. - DbUtils.closeQuietly(connection); - } + void installH2() { + Connection connection = null; + try (SqlSession session = dbClient.openSession(false)) { + connection = session.getConnection(); + createSchema(connection, dbClient.getDatabase().getDialect().getId()); + } finally { + DbUtils.closeQuietly(connection); } - return false; } @VisibleForTesting protected void createSchema(Connection connection, String dialectId) { DdlUtils.createSchema(connection, dialectId, false); } + + @Override + public void stop() { + // nothing to do + } } diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/AutoDbMigrationTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/AutoDbMigrationTest.java new file mode 100644 index 00000000000..093d6541ad9 --- /dev/null +++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/AutoDbMigrationTest.java @@ -0,0 +1,144 @@ +/* + * SonarQube + * Copyright (C) 2009-2016 SonarSource SA + * mailto:contact AT sonarsource DOT com + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.platform.db.migration; + +import java.sql.Connection; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.mockito.Mockito; +import org.sonar.api.platform.ServerUpgradeStatus; +import org.sonar.api.utils.log.LogTester; +import org.sonar.api.utils.log.LoggerLevel; +import org.sonar.db.DbClient; +import org.sonar.db.DbSession; +import org.sonar.db.dialect.Dialect; +import org.sonar.db.dialect.H2; +import org.sonar.db.dialect.MsSql; +import org.sonar.db.dialect.MySql; +import org.sonar.db.dialect.Oracle; +import org.sonar.db.dialect.PostgreSql; +import org.sonar.server.platform.db.migration.engine.MigrationEngine; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; +import static org.mockito.Mockito.verifyZeroInteractions; +import static org.mockito.Mockito.when; + +public class AutoDbMigrationTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + @Rule + public LogTester logTester = new LogTester(); + + private DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS); + private ServerUpgradeStatus serverUpgradeStatus = mock(ServerUpgradeStatus.class); + private MigrationEngine migrationEngine = mock(MigrationEngine.class); + private AutoDbMigration underTest = new AutoDbMigration(serverUpgradeStatus, dbClient, migrationEngine); + + private AutoDbMigration noRealH2Creation = spy(new AutoDbMigration(serverUpgradeStatus, dbClient, migrationEngine) { + @Override + protected void createSchema(Connection connection, String dialectId) { + } + }); + + @Test + public void start_creates_schema_on_h2_if_fresh_install() { + mockDialect(new H2()); + mockDbClientOpenSession(); + mockFreshInstall(true); + + noRealH2Creation.start(); + + verify(noRealH2Creation).installH2(); + verifyInfoLog(); + } + + @Test + public void start_runs_MigrationEngine_on_mysql_if_fresh_install() { + start_runs_MigrationEngine_for_dialect_if_fresh_install(new MySql()); + } + + @Test + public void start_runs_MigrationEngine_on_postgre_if_fresh_install() { + start_runs_MigrationEngine_for_dialect_if_fresh_install(new PostgreSql()); + } + + @Test + public void start_runs_MigrationEngine_on_Oracle_if_fresh_install() { + start_runs_MigrationEngine_for_dialect_if_fresh_install(new Oracle()); + } + + @Test + public void start_runs_MigrationEngine_on_MsSQL_if_fresh_install() { + start_runs_MigrationEngine_for_dialect_if_fresh_install(new MsSql()); + } + + private void start_runs_MigrationEngine_for_dialect_if_fresh_install(Dialect dialect) { + mockDialect(dialect); + mockFreshInstall(true); + + underTest.start(); + + verify(migrationEngine).execute(); + verifyInfoLog(); + } + + @Test + public void start_does_nothing_if_not_fresh_install() { + mockFreshInstall(false); + + noRealH2Creation.start(); + + verify(noRealH2Creation).start(); + verifyNoMoreInteractions(noRealH2Creation); + verifyZeroInteractions(migrationEngine); + assertThat(logTester.logs()).isEmpty(); + } + + @Test + public void stop_has_no_effect() { + underTest.stop(); + } + + private void mockFreshInstall(boolean value) { + when(serverUpgradeStatus.isFreshInstall()).thenReturn(value); + } + + private void mockDialect(Dialect dialect) { + when(dbClient.getDatabase().getDialect()).thenReturn(dialect); + } + + private void mockDbClientOpenSession() { + Connection connection = mock(Connection.class); + DbSession session = mock(DbSession.class); + when(session.getConnection()).thenReturn(connection); + when(dbClient.openSession(false)).thenReturn(session); + } + + private void verifyInfoLog() { + assertThat(logTester.logs()).hasSize(1); + assertThat(logTester.logs(LoggerLevel.INFO)).containsExactly("Automatically perform DB migration on fresh install"); + } + +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index c1980a69c19..44129da9357 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -31,7 +31,6 @@ import org.sonar.api.utils.log.Loggers; import org.sonar.api.utils.log.Profiler; import org.sonar.core.platform.ComponentContainer; import org.sonar.db.version.DatabaseVersion; -import org.sonar.server.platform.db.migration.engine.MigrationEngine; import org.sonar.server.platform.platformlevel.PlatformLevel; import org.sonar.server.platform.platformlevel.PlatformLevel1; import org.sonar.server.platform.platformlevel.PlatformLevel2; @@ -40,8 +39,6 @@ import org.sonar.server.platform.platformlevel.PlatformLevel4; import org.sonar.server.platform.platformlevel.PlatformLevelSafeMode; import org.sonar.server.platform.platformlevel.PlatformLevelStartup; -import static com.google.common.base.Preconditions.checkState; - /** * @since 2.2 */ @@ -97,12 +94,6 @@ public class Platform { } } - public void upgradeDb() { - checkState(isInSafeMode(), "Must be in safe mode"); - MigrationEngine migrationEngine = currentLevel.getContainer().getComponentByType(MigrationEngine.class); - migrationEngine.execute(); - } - // Platform is injected in Pico, so do not rename this method "start" public void doStart() { doStart(Startup.ALL); @@ -113,12 +104,19 @@ public class Platform { return; } + boolean wentToSafemode = false; if (requireSafeMode()) { LOGGER.info("DB needs migration, entering safe mode"); + wentToSafemode = true; startSafeModeContainer(); currentLevel = levelSafeMode; started = true; - } else { + } + // if AutoDbMigration kicked in, safe mode is not required anymore + if (!requireSafeMode()) { + if (wentToSafemode) { + LOGGER.info("DB has been updated, exiting safe mode"); + } startLevel34Containers(); executeStartupTasks(startup); // switch current container last to avoid giving access to a partially initialized container diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java index 193f8fd6143..db6f61bab79 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/db/migration/DatabaseMigrationImpl.java @@ -24,9 +24,10 @@ import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.ReentrantLock; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; -import org.sonar.api.utils.log.Profiler; +import org.sonar.core.util.logs.Profiler; import org.sonar.server.platform.Platform; import org.sonar.server.platform.db.migration.engine.MigrationEngine; +import org.sonar.server.platform.db.migration.step.MigrationStepExecutionException; import org.sonar.server.ruby.RubyBridge; import static org.sonar.server.platform.db.migration.DatabaseMigrationState.Status; @@ -103,26 +104,33 @@ public class DatabaseMigrationImpl implements DatabaseMigration { migrationState.setError(null); Profiler profiler = Profiler.create(LOGGER); try { - profiler.startInfo("Starting DB Migration"); + profiler.startInfo("Starting DB Migration and container restart"); doUpgradeDb(); doRestartContainer(); doRecreateWebRoutes(); migrationState.setStatus(Status.SUCCEEDED); - profiler.stopInfo("DB Migration ended successfully"); + profiler.stopInfo("DB Migration and container restart: success"); + } catch (MigrationStepExecutionException e) { + profiler.stopError("DB migration failed"); + LOGGER.error("DB migration ended with an exception", e); + saveStatus(e); } catch (Throwable t) { - profiler.stopInfo("DB migration failed"); - LOGGER.error("DB Migration or container restart failed. Process ended with an exception", t); - migrationState.setStatus(Status.FAILED); - migrationState.setError(t); + profiler.stopError("Container restart failed"); + LOGGER.error("Container restart failed", t); + saveStatus(t); } finally { running.getAndSet(false); } } + private void saveStatus(Throwable e) { + migrationState.setStatus(Status.FAILED); + migrationState.setError(e); + } + private void doUpgradeDb() { Profiler profiler = Profiler.createIfTrace(LOGGER); profiler.startTrace("Starting DB Migration"); - rubyBridge.databaseMigration().trigger(); migrationEngine.execute(); profiler.stopTrace("DB Migration ended"); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/package-info.java deleted file mode 100644 index e7c8a6c58ef..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/db/migrations/package-info.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -@ParametersAreNonnullByDefault -package org.sonar.server.platform.db.migrations; - -import javax.annotation.ParametersAreNonnullByDefault; diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java index 65b92653ff3..5c314bda2c0 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java @@ -31,7 +31,6 @@ import org.sonar.server.platform.db.CheckDatabaseCharsetAtStartup; import org.sonar.server.platform.db.migration.DatabaseMigrationExecutorServiceImpl; import org.sonar.server.platform.db.migration.DatabaseMigrationStateImpl; import org.sonar.server.platform.db.migration.history.MigrationHistoryTableImpl; -import org.sonar.server.platform.db.migrations.DatabaseMigrator; import org.sonar.server.platform.web.RailsAppsDeployer; import org.sonar.server.plugins.InstalledPluginReferentialFactory; import org.sonar.server.plugins.ServerPluginJarExploder; @@ -69,12 +68,10 @@ public class PlatformLevel2 extends PlatformLevel { DefaultI18n.class, RuleI18nManager.class); - // Full Java DB Migration framework and configuration + // DB Migration framework dependencies required at level2 addIfStartupLeader(MigrationHistoryTableImpl.class); add(DatabaseMigrationStateImpl.class, DatabaseMigrationExecutorServiceImpl.class); - // Ruby DB Migration - add(DatabaseMigrator.class); addIfStartupLeader( DatabaseCharsetChecker.class, diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java index c9043e0008d..e83661bdcaa 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java @@ -22,6 +22,7 @@ package org.sonar.server.platform.platformlevel; import org.sonar.server.organization.NoopDefaultOrganizationCache; import org.sonar.server.platform.ServerImpl; import org.sonar.server.platform.db.migration.DatabaseMigrationImpl; +import org.sonar.server.platform.db.migration.AutoDbMigration; import org.sonar.server.platform.db.migration.MigrationEngineModule; import org.sonar.server.platform.db.migration.version.DbVersionModule; import org.sonar.server.platform.web.WebPagesFilter; @@ -66,6 +67,8 @@ public class PlatformLevelSafeMode extends PlatformLevel { NoopDefaultOrganizationCache.class); add(DatabaseMigrationImpl.class); add(DbVersionModule.class); - addIfStartupLeader(MigrationEngineModule.class); + addIfStartupLeader( + MigrationEngineModule.class, + AutoDbMigration.class); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/ruby/CallDatabaseVersionUpgrade.java b/server/sonar-server/src/main/java/org/sonar/server/ruby/CallDatabaseVersionUpgrade.java deleted file mode 100644 index 47df55d661b..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/ruby/CallDatabaseVersionUpgrade.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.ruby; - -/** - * Interface which must be top-level public class to be used by the Ruby engine but that hides name of the Ruby method - * in the Ruby script from the rest of the platform (only {@link RubyDatabaseMigration} is known to the platform). - */ -@FunctionalInterface -public interface CallDatabaseVersionUpgrade { - - /** - * Java method that calls the upgrade_and_start method defined in the {@code call_databaseversion_upgrade.rb} script. - */ - void callUpgrade(); -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/ruby/PlatformRubyBridge.java b/server/sonar-server/src/main/java/org/sonar/server/ruby/PlatformRubyBridge.java index 33d81ddd27b..cabdf149442 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ruby/PlatformRubyBridge.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ruby/PlatformRubyBridge.java @@ -30,7 +30,6 @@ import org.jruby.javasupport.JavaUtil; import org.jruby.runtime.builtin.IRubyObject; public class PlatformRubyBridge implements RubyBridge { - private static final String CALL_UPGRADE_AND_START_RB_FILENAME = "call_databaseversion_upgrade.rb"; private static final String CALL_LOAD_JAVA_WEB_SERVICES_RB_FILENAME = "call_load_java_web_services.rb"; private static final String CALL_INVALIDATE_METRIC_CACHE_RB_FILENAME = "call_invalidate_metric_cache.rb"; @@ -40,15 +39,6 @@ public class PlatformRubyBridge implements RubyBridge { this.rackBridge = rackBridge; } - @Override - public RubyDatabaseMigration databaseMigration() { - CallDatabaseVersionUpgrade callDatabaseVersionUpgrade = parseMethodScriptToInterface( - CALL_UPGRADE_AND_START_RB_FILENAME, CallDatabaseVersionUpgrade.class - ); - - return callDatabaseVersionUpgrade::callUpgrade; - } - @Override public RubyRailsRoutes railsRoutes() { CallLoadJavaWebServices callLoadJavaWebServices = parseMethodScriptToInterface( diff --git a/server/sonar-server/src/main/java/org/sonar/server/ruby/RubyBridge.java b/server/sonar-server/src/main/java/org/sonar/server/ruby/RubyBridge.java index 327fe976baa..47cc44b0833 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ruby/RubyBridge.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ruby/RubyBridge.java @@ -24,12 +24,6 @@ package org.sonar.server.ruby; * Java object which an underlying Ruby implementation. */ public interface RubyBridge { - /** - * Returns a wrapper class that allows calling the database migration in Ruby. - * - * @return a {@link RubyDatabaseMigration} - */ - RubyDatabaseMigration databaseMigration(); /** * Returns a class that allows calling the (re)creation of web routes in Rails. diff --git a/server/sonar-server/src/main/java/org/sonar/server/ruby/RubyDatabaseMigration.java b/server/sonar-server/src/main/java/org/sonar/server/ruby/RubyDatabaseMigration.java deleted file mode 100644 index bc999bed994..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/ruby/RubyDatabaseMigration.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.ruby; - -/** - * Represents the database migration written in Ruby. - */ -@FunctionalInterface -public interface RubyDatabaseMigration { - /** - * Triggers the Ruby migration with ActiveRecord. - * This is not thread safe! - */ - void trigger(); -} diff --git a/server/sonar-server/src/main/resources/org/sonar/server/ruby/call_databaseversion_upgrade.rb b/server/sonar-server/src/main/resources/org/sonar/server/ruby/call_databaseversion_upgrade.rb deleted file mode 100644 index b53c60d034a..00000000000 --- a/server/sonar-server/src/main/resources/org/sonar/server/ruby/call_databaseversion_upgrade.rb +++ /dev/null @@ -1,12 +0,0 @@ -# this script defines a method which calls the class method "upgrade" of the DatabaseVersion class defined -# in /server/sonar-web/src/main/webapp/WEB-INF/lib/database_version.rb - -require 'database_version' - -class RbCallUpgrade - include Java::org.sonar.server.ruby.CallDatabaseVersionUpgrade - def call_upgrade - DatabaseVersion.upgrade - end -end -RbCallUpgrade.new \ No newline at end of file diff --git a/server/sonar-server/src/main/resources/org/sonar/server/ruby/call_invalidate_metric_cache.rb b/server/sonar-server/src/main/resources/org/sonar/server/ruby/call_invalidate_metric_cache.rb index c062d0e29af..6ead450460b 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/ruby/call_invalidate_metric_cache.rb +++ b/server/sonar-server/src/main/resources/org/sonar/server/ruby/call_invalidate_metric_cache.rb @@ -1,6 +1,9 @@ # this script defines a method which calls the class method "clear_cache" of the Metric class defined # in /server/sonar-web/src/main/webapp/WEB-INF/app/models/metric.rb +# this essentially makes UT work, must be a file that actually exists in production +require 'database_version' + class RbCallInvalidateMetricCache include Java::org.sonar.server.ruby.CallInvalidateMetricCache def call_invalidate diff --git a/server/sonar-server/src/main/resources/org/sonar/server/ruby/call_load_java_web_services.rb b/server/sonar-server/src/main/resources/org/sonar/server/ruby/call_load_java_web_services.rb index 7623e055931..ddd01c92495 100644 --- a/server/sonar-server/src/main/resources/org/sonar/server/ruby/call_load_java_web_services.rb +++ b/server/sonar-server/src/main/resources/org/sonar/server/ruby/call_load_java_web_services.rb @@ -1,12 +1,13 @@ -# this script defines a method which calls the class method "load_java_web_services" of the DatabaseVersion class -# definedin /server/sonar-web/src/main/webapp/WEB-INF/lib/database_version.rb +# this script defines a method which calls the class method "load_java_web_services" of the Bootstrap class +# definedin /server/sonar-web/src/main/webapp/WEB-INF/config/environment.rb +# this essentially makes UT work, must be a file that actually exists in production require 'database_version' class RbCallLoadJavaWebServices include Java::org.sonar.server.ruby.CallLoadJavaWebServices def call_load_java_web_services - DatabaseVersion.load_java_web_services + Bootstrap.load_java_web_services end end -RbCallLoadJavaWebServices.new \ No newline at end of file +RbCallLoadJavaWebServices.new diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplConcurrentAccessTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplConcurrentAccessTest.java index 1b27e76ecd1..1c2047ebf2b 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplConcurrentAccessTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplConcurrentAccessTest.java @@ -19,7 +19,6 @@ */ package org.sonar.server.platform.db.migration; -import com.google.common.base.Throwables; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -30,7 +29,6 @@ import org.junit.Test; import org.sonar.server.platform.Platform; import org.sonar.server.platform.db.migration.engine.MigrationEngine; import org.sonar.server.ruby.RubyBridge; -import org.sonar.server.ruby.RubyDatabaseMigration; import org.sonar.server.ruby.RubyRailsRoutes; import static org.assertj.core.api.Assertions.assertThat; @@ -55,24 +53,6 @@ public class DatabaseMigrationImplConcurrentAccessTest { command.run(); } }; - /** - * thread-safe counter of calls to the trigger method of {@link #rubyDatabaseMigration} - */ - private AtomicInteger triggerCount = new AtomicInteger(); - /** - * Implementation of RubyDatabaseMigration which trigger method increments a thread-safe counter and add a delay of 200ms - */ - private RubyDatabaseMigration rubyDatabaseMigration = new RubyDatabaseMigration() { - @Override - public void trigger() { - triggerCount.incrementAndGet(); - try { - Thread.currentThread().sleep(1000); - } catch (InterruptedException e) { - Throwables.propagate(e); - } - } - }; private MutableDatabaseMigrationState migrationState = mock(MutableDatabaseMigrationState.class); private RubyBridge rubyBridge = mock(RubyBridge.class); private Platform platform = mock(Platform.class); @@ -87,11 +67,20 @@ public class DatabaseMigrationImplConcurrentAccessTest { @Test public void two_concurrent_calls_to_startit_call_trigger_only_once() throws Exception { - when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration); + AtomicInteger triggerCount = new AtomicInteger(); + MigrationEngine incrementingMigrationEngine = new MigrationEngine() { + + @Override + public void execute() { + triggerCount.incrementAndGet(); + } + }; when(rubyBridge.railsRoutes()).thenReturn(railsRoutes); - pool.submit(new CallStartit()); - pool.submit(new CallStartit()); + DatabaseMigrationImpl underTest = new DatabaseMigrationImpl(executorService, migrationState, rubyBridge, incrementingMigrationEngine, platform); + + pool.submit(new CallStartit(underTest)); + pool.submit(new CallStartit(underTest)); pool.awaitTermination(2, TimeUnit.SECONDS); @@ -99,6 +88,12 @@ public class DatabaseMigrationImplConcurrentAccessTest { } private class CallStartit implements Runnable { + private final DatabaseMigrationImpl underTest; + + private CallStartit(DatabaseMigrationImpl underTest) { + this.underTest = underTest; + } + @Override public void run() { latch.countDown(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplTest.java index 3704d8c7761..c5d12e79f3e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/db/migration/DatabaseMigrationImplTest.java @@ -25,7 +25,6 @@ import org.mockito.InOrder; import org.sonar.server.platform.Platform; import org.sonar.server.platform.db.migration.engine.MigrationEngine; import org.sonar.server.ruby.RubyBridge; -import org.sonar.server.ruby.RubyDatabaseMigration; import org.sonar.server.ruby.RubyRailsRoutes; import static org.assertj.core.api.Assertions.assertThat; @@ -52,23 +51,20 @@ public class DatabaseMigrationImplTest { }; private MutableDatabaseMigrationState migrationState = new DatabaseMigrationStateImpl(); private RubyBridge rubyBridge = mock(RubyBridge.class); - private RubyDatabaseMigration rubyDatabaseMigration = mock(RubyDatabaseMigration.class); private RubyRailsRoutes rubyRailsRoutes = mock(RubyRailsRoutes.class); private Platform platform = mock(Platform.class); private MigrationEngine migrationEngine = mock(MigrationEngine.class); - private InOrder inOrder = inOrder(rubyDatabaseMigration, rubyBridge, rubyRailsRoutes, platform, migrationEngine); + private InOrder inOrder = inOrder(rubyBridge, rubyRailsRoutes, platform, migrationEngine); private DatabaseMigrationImpl underTest = new DatabaseMigrationImpl(executorService, migrationState, rubyBridge, migrationEngine, platform); @Test - public void startit_calls_databasemigration_trigger_in_a_separate_thread() { - when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration); + public void startit_calls_MigrationEngine_execute() { when(rubyBridge.railsRoutes()).thenReturn(rubyRailsRoutes); underTest.startIt(); - inOrder.verify(rubyBridge).databaseMigration(); - inOrder.verify(rubyDatabaseMigration).trigger(); + inOrder.verify(migrationEngine).execute(); inOrder.verify(platform).doStart(); inOrder.verify(rubyBridge).railsRoutes(); inOrder.verify(rubyRailsRoutes).recreate(); @@ -77,7 +73,6 @@ public class DatabaseMigrationImplTest { @Test public void status_is_SUCCEEDED_and_failure_is_null_when_trigger_runs_without_an_exception() { - when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration); when(rubyBridge.railsRoutes()).thenReturn(rubyRailsRoutes); underTest.startIt(); @@ -89,7 +84,7 @@ public class DatabaseMigrationImplTest { @Test public void status_is_FAILED_and_failure_stores_the_exception_when_trigger_throws_an_exception() { - mockTriggerThrowsError(); + mockMigrationThrowsError(); underTest.startIt(); @@ -100,7 +95,7 @@ public class DatabaseMigrationImplTest { @Test public void successive_calls_to_startIt_reset_status_startedAt_and_failureError() { - mockTriggerThrowsError(); + mockMigrationThrowsError(); underTest.startIt(); @@ -109,7 +104,7 @@ public class DatabaseMigrationImplTest { Date firstStartDate = migrationState.getStartedAt(); assertThat(firstStartDate).isNotNull(); - mockTriggerDoesNothing(); + mockMigrationDoesNothing(); underTest.startIt(); @@ -118,15 +113,13 @@ public class DatabaseMigrationImplTest { assertThat(migrationState.getStartedAt()).isNotSameAs(firstStartDate); } - private void mockTriggerThrowsError() { - when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration); - doThrow(AN_ERROR).when(rubyDatabaseMigration).trigger(); + private void mockMigrationThrowsError() { + doThrow(AN_ERROR).when(migrationEngine).execute(); when(rubyBridge.railsRoutes()).thenReturn(rubyRailsRoutes); } - private void mockTriggerDoesNothing() { - when(rubyBridge.databaseMigration()).thenReturn(rubyDatabaseMigration); - doNothing().when(rubyDatabaseMigration).trigger(); + private void mockMigrationDoesNothing() { + doNothing().when(migrationEngine).execute(); when(rubyBridge.railsRoutes()).thenReturn(rubyRailsRoutes); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/DatabaseMigratorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/DatabaseMigratorTest.java deleted file mode 100644 index a3bac3ac496..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/db/migrations/DatabaseMigratorTest.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * SonarQube - * Copyright (C) 2009-2016 SonarSource SA - * mailto:contact AT sonarsource DOT com - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.platform.db.migrations; - -import java.sql.Connection; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.mockito.Mockito; -import org.sonar.api.platform.ServerUpgradeStatus; -import org.sonar.db.DbClient; -import org.sonar.db.DbSession; -import org.sonar.db.dialect.Dialect; -import org.sonar.db.dialect.H2; -import org.sonar.db.dialect.MySql; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.anyBoolean; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class DatabaseMigratorTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - private DbClient dbClient = mock(DbClient.class, Mockito.RETURNS_DEEP_STUBS); - private ServerUpgradeStatus serverUpgradeStatus = mock(ServerUpgradeStatus.class); - private DatabaseMigrator migrator; - - @Before - public void setUp() { - migrator = new DatabaseMigrator(dbClient, serverUpgradeStatus, null); - } - - @Test - public void should_support_only_creation_of_h2_database() { - when(dbClient.getDatabase().getDialect()).thenReturn(new MySql()); - - assertThat(migrator.createDatabase()).isFalse(); - verify(dbClient, never()).openSession(anyBoolean()); - } - - @Test - public void should_create_schema_on_h2() { - Dialect supportedDialect = new H2(); - when(dbClient.getDatabase().getDialect()).thenReturn(supportedDialect); - Connection connection = mock(Connection.class); - DbSession session = mock(DbSession.class); - when(session.getConnection()).thenReturn(connection); - when(dbClient.openSession(false)).thenReturn(session); - when(serverUpgradeStatus.isFreshInstall()).thenReturn(true); - - DatabaseMigrator databaseMigrator = new DatabaseMigrator(dbClient, serverUpgradeStatus, null) { - @Override - protected void createSchema(Connection connection, String dialectId) { - } - }; - - assertThat(databaseMigrator.createDatabase()).isTrue(); - } - -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/ruby/PlatformRubyBridgeTest.java b/server/sonar-server/src/test/java/org/sonar/server/ruby/PlatformRubyBridgeTest.java index e0e21365f3f..1c51261f12e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/ruby/PlatformRubyBridgeTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/ruby/PlatformRubyBridgeTest.java @@ -61,19 +61,6 @@ public class PlatformRubyBridgeTest { } } - /** - * unit test only makes sure the wrapping and method forwarding provided by JRuby works so building the - * RubyDatabaseMigration object and calling its trigger method is enough as it would otherwise raise an exception - */ - @Test - public void testDatabaseMigration() { - try { - underTest.databaseMigration().trigger(); - } catch (RaiseException e) { - throw new RuntimeException("Loading error with container loadPath " + container.getLoadPaths(), e); - } - } - /** * unit test only makes sure the wrapping and method forwarding provided by JRuby works so building the * RubyRailsRoutes object and calling its trigger method is enough as it would otherwise raise an exception diff --git a/server/sonar-server/src/test/resources/org/sonar/server/ruby/database_version.rb b/server/sonar-server/src/test/resources/org/sonar/server/ruby/database_version.rb index 430c218cd33..c55ff00a97a 100644 --- a/server/sonar-server/src/test/resources/org/sonar/server/ruby/database_version.rb +++ b/server/sonar-server/src/test/resources/org/sonar/server/ruby/database_version.rb @@ -1,11 +1,7 @@ -# a dummy class DatabaseVersion that "mocks" the DatabaseVersion class of the platform by defining a class method called upgrade_and_start +# a dummy class Bootstrap that "mocks" the Bootstrap class of the platform by defining a class method called upgrade_and_start # unit test only makes sure the wrapping and method forwarding provided by JRuby works so providing an empty method is enough as # it would otherwise raise an exception -class DatabaseVersion - - def self.upgrade - - end +class Bootstrap def self.load_java_web_services diff --git a/server/sonar-web/src/main/webapp/WEB-INF/config/environment.rb b/server/sonar-web/src/main/webapp/WEB-INF/config/environment.rb index 00f28b34ec3..b355a78aa1d 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/config/environment.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/config/environment.rb @@ -127,25 +127,43 @@ class ActionView::Base alias j json_escape end +class Bootstrap + + def self.load_java_web_services + ActionController::Routing::Routes.add_java_ws_routes + end + + def self.dbUpgradeRequired? + !Java::OrgSonarServerUi::JRubyFacade.getInstance().isDbUptodate() + end + + def self.safe_mode_start + load_java_web_services + end + + def self.regular_start + Java::OrgSonarServerPlatform::Platform.getInstance().doStart() + load_java_web_services + end + + def self.boostrap + if dbUpgradeRequired? + safe_mode_start() + else + regular_start() + end + end + +end # # other patches : -# - activerecord : fix Oracle bug when more than 1000 elements in IN clause. See lib/active_record/association_preload.rb -# See https://github.com/rails/rails/issues/585 # - actionview NumberHelper, patch for number_with_precision() require File.dirname(__FILE__) + '/../lib/java_ws_routing.rb' require File.dirname(__FILE__) + '/../lib/database_version.rb' -DatabaseVersion.automatic_setup - +Bootstrap.boostrap -# -# -# IMPORTANT NOTE -# Some changes have been done in activerecord-jdbc-adapter. Most of them relate to column types. -# All these changes are prefixed by the comment #sonar -# -# # Increase size of form parameters # See http://jira.sonarsource.com/browse/SONAR-5577 diff --git a/server/sonar-web/src/main/webapp/WEB-INF/lib/database_version.rb b/server/sonar-web/src/main/webapp/WEB-INF/lib/database_version.rb index 9656163835a..bb9e17f3fb1 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/lib/database_version.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/lib/database_version.rb @@ -72,15 +72,6 @@ class DatabaseVersion ActionController::Routing::Routes.add_java_ws_routes end - def self.automatic_setup - if current_version<=0 - upgrade_and_start() - else - load_java_web_services - end - uptodate? - end - def self.connected? ActiveRecord::Base.connected? end -- 2.39.5