From: Sébastien Lesaint Date: Tue, 14 Apr 2015 14:43:59 +0000 (+0200) Subject: SONAR-6366 make Java WS available when DB migration is needed X-Git-Tag: 5.2-RC1~2063 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=b3b8a174238de179380ff3af762a767063d9ae67;p=sonarqube.git SONAR-6366 make Java WS available when DB migration is needed introduce a new "level" in ServerComponents called safe-mode which is started in place of level3 and 4 when DB needs to be migrated update ROR code so that Java WS are always started by ROR, even when DB needs an upgrade. In such case, only Java Webservices which supports DB to be in such state are now instanced in addition, make sure the currentContainer is pointing at all times to a fully initialized container chain (one ending with level4 or safemode), this ensure there is always valid components to support the UI and WS calls PlatformDatabaseMigration is part of level2 in order to have the same instance share between safe mode container and level3/4 containers therefor avoiding loosing the migration status once the migration is copmplete and the Platform#doStart() is called refactor Platform code to separate current container switch from start/stop of containers --- 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 8523ec887b7..5d857910be2 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 @@ -19,25 +19,27 @@ */ package org.sonar.server.platform; +import java.util.Collection; +import java.util.Properties; +import javax.annotation.CheckForNull; +import javax.servlet.ServletContext; import org.sonar.api.platform.ComponentContainer; import org.sonar.api.platform.Server; +import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.core.persistence.DatabaseVersion; -import javax.annotation.CheckForNull; -import javax.servlet.ServletContext; -import java.util.Collection; -import java.util.Properties; - /** * @since 2.2 */ public class Platform { + private static final Logger LOGGER = Loggers.get(Platform.class); + private static final Platform INSTANCE = new Platform(); private ServerComponents serverComponents; - private ComponentContainer level1Container, level2Container, level3Container, level4Container; + private ComponentContainer level1Container, level2Container, safeModeContainer, level3Container, level4Container; private ComponentContainer currentContainer; private boolean dbConnected = false; private boolean started = false; @@ -72,69 +74,137 @@ public class Platform { if (!dbConnected) { startLevel1Container(); startLevel2Container(); + currentContainer = level2Container; dbConnected = true; } } // Platform is injected in Pico, so do not rename this method "start" public void doStart() { - if (!started && getDatabaseStatus() == DatabaseVersion.Status.UP_TO_DATE) { + if (started && !isInSafeMode()) { + return; + } + + if (requireSafeMode()) { + LOGGER.info("DB needs migration, entering safe mode"); + startSafeModeContainer(); + currentContainer = safeModeContainer; + started = true; + } else { startLevel34Containers(); + executeStartupTasks(); + // switch current container last to avoid giving access to a partially initialized container + currentContainer = level4Container; started = true; + + // stop safemode container if it existed + stopSafeModeContainer(); } } + public void restart() { + // switch currentContainer on level1 now to avoid exposing a container in the process of stopping + currentContainer = level1Container; + + // stop containers + stopSafeModeContainer(); + stopLevel234Containers(); + + // no need to initialize database connection, so level 1 is skipped + startLevel2Container(); + startLevel34Containers(); + executeStartupTasks(); + currentContainer = level4Container; + } + + private boolean requireSafeMode() { + return getDatabaseStatus() != DatabaseVersion.Status.UP_TO_DATE; + } + public boolean isStarted() { - return started; + return started && !isInSafeMode(); + } + + public boolean isInSafeMode() { + return started && safeModeContainer != null && currentContainer == safeModeContainer; } /** - * Start level 1 only + * Starts level 1 */ private void startLevel1Container() { level1Container = new ComponentContainer(); level1Container.addSingletons(serverComponents.level1Components()); level1Container.startComponents(); - currentContainer = level1Container; } /** - * Start level 2 only + * Starts level 2 */ private void startLevel2Container() { level2Container = level1Container.createChild(); level2Container.addSingletons(serverComponents.level2Components()); level2Container.startComponents(); - currentContainer = level2Container; } /** - * Start level 3 and greater + * Starts level 3 and 4 */ private void startLevel34Containers() { level3Container = level2Container.createChild(); level3Container.addSingletons(serverComponents.level3Components()); level3Container.startComponents(); - currentContainer = level3Container; level4Container = level3Container.createChild(); serverComponents.startLevel4Components(level4Container); - currentContainer = level4Container; - executeStartupTasks(); } public void executeStartupTasks() { serverComponents.executeStartupTasks(level4Container); } - public void restart() { - // Do not need to initialize database connection, so level 1 is skipped + private void startSafeModeContainer() { + safeModeContainer = level2Container.createChild(); + safeModeContainer.addSingletons(serverComponents.safeModeComponents()); + safeModeContainer.startComponents(); + } + + /** + * Stops level 1 + */ + private void stopLevel1Container() { + if (level1Container != null) { + level1Container.stopComponents(); + level1Container = null; + } + } + + /** + * Stops level 2, 3 and 3 containers cleanly if they exists. + * Call this method before {@link #startLevel1Container()} to avoid duplicate attempt to stop safemode container + * components (since calling stop on a container calls stop on its children too, see + * {@link ComponentContainer#stopComponents()}). + */ + private void stopLevel234Containers() { if (level2Container != null) { level2Container.stopComponents(); - currentContainer = level1Container; + level2Container = null; + level3Container = null; + level4Container = null; + } + } + + /** + * Stops safemode container cleanly if it exists. + * Call this method before {@link #stopLevel234Containers()} and {@link #stopLevel1Container()} to avoid duplicate + * attempt to stop safemode container components (since calling stop on a container calls stops on its children too, + * see {@link ComponentContainer#stopComponents()}). + */ + private void stopSafeModeContainer() { + if (safeModeContainer != null) { + safeModeContainer.stopComponents(); + safeModeContainer = null; } - startLevel2Container(); - startLevel34Containers(); } private DatabaseVersion.Status getDatabaseStatus() { @@ -144,16 +214,15 @@ public class Platform { // Do not rename "stop" public void doStop() { - if (level1Container != null) { - try { - level1Container.stopComponents(); - level1Container = null; - currentContainer = null; - dbConnected = false; - started = false; - } catch (Exception e) { - Loggers.get(getClass()).debug("Fail to stop server - ignored", e); - } + try { + stopSafeModeContainer(); + stopLevel234Containers(); + stopLevel1Container(); + currentContainer = null; + dbConnected = false; + started = false; + } catch (Exception e) { + LOGGER.debug("Fail to stop server - ignored", e); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java index 9e206af2bee..2ceda78a3d4 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java @@ -528,7 +528,42 @@ class ServerComponents { JRubyI18n.class, DefaultI18n.class, RuleI18nManager.class, - Durations.class + Durations.class, + + // DB migration + PlatformDatabaseMigrationExecutorServiceImpl.class, + PlatformDatabaseMigration.class + + ); + } + + /** + * These components allow minimum services to be provided by SonarQube when database is not up-to-date and + * level3/4 components can therefore NOT be loaded. They rely on level1 and level2 components. + *

+ * Available components: + *

+ *

+ */ + public Collection safeModeComponents() { + return Lists.newArrayList( + + // DB access required by DatabaseSessionFilter wired into ROR + DefaultDatabaseConnector.class, + ThreadLocalDatabaseSessionFactory.class, + + // Server WS + MigrateDbSystemWsAction.class, + SystemWs.class, + + // Listing WS + ListingWs.class, + + // WS engine + WebServiceEngine.class ); } @@ -545,10 +580,7 @@ class ServerComponents { ServerMetadataPersister.class, DefaultHttpDownloader.class, UriReader.class, - ServerIdGenerator.class, - - PlatformDatabaseMigrationExecutorServiceImpl.class, - PlatformDatabaseMigration.class + ServerIdGenerator.class ); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java b/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java index 38d2dce668a..0015c8fb34e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java +++ b/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java @@ -222,6 +222,18 @@ public class ServerTester extends ExternalResource { } } + private void checkInSafeMode() { + if (platform == null || !platform.isInSafeMode()) { + throw new IllegalStateException("Not in safe mode"); + } + } + + private void checkNotInSafeMode() { + if (platform != null && platform.isInSafeMode()) { + throw new IllegalStateException("Already in safe mode"); + } + } + public static class Xoo implements Language { public static final String KEY = "xoo"; diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/java_ws_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/java_ws_controller.rb index 4be0899f0b4..6fe8c930626 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/java_ws_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/java_ws_controller.rb @@ -23,6 +23,10 @@ class Api::JavaWsController < Api::ApiController before_filter :check_authentication, :unless => ['skip_authentication_check_for_batch'] + # no need to check if WS can be accessed when DB is not up-to-date, this is dealt with in + # Platform and ServerComponents classes + skip_before_filter :check_database_version + def index ws_request = Java::OrgSonarServerWs::ServletRequest.new(servlet_request, params.to_java) ws_response = Java::OrgSonarServerWs::ServletResponse.new() 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 e76c817d444..9473dfe3356 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 @@ -71,7 +71,7 @@ class DatabaseVersion def self.automatic_setup if current_version<=0 upgrade_and_start() - elsif uptodate? + else load_java_web_services end uptodate?