]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6366 make Java WS available when DB migration is needed
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 14 Apr 2015 14:43:59 +0000 (16:43 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 5 May 2015 07:18:54 +0000 (09:18 +0200)
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

server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java
server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/java_ws_controller.rb
server/sonar-web/src/main/webapp/WEB-INF/lib/database_version.rb

index 8523ec887b7cf7e65e0fa9ba6c112f3d4d1a226f..5d857910be2605319fd3688bcb7af949543499a7 100644 (file)
  */
 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);
     }
   }
 
index 9e206af2beee45855bcc8e958e51539777830cf0..2ceda78a3d4fd2e30234e5fa6940bfc856cfe346 100644 (file)
@@ -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.
+   * <p>
+   * Available components:
+   * <ul>
+   *   <li>WS to query status of the server and upgrade the DB and their dependencies</li>
+   *   <li>WS listing Webservice to allow user to list WebServices available in safemode</li>
+   * </ul>
+   * </p>
+   */
+  public Collection<Object> safeModeComponents() {
+    return Lists.<Object>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
       );
   }
 
index 38d2dce668a19288c30c335b698e70aa46c6e449..0015c8fb34ee0fef64bdf9fb37bbdc114321f547 100644 (file)
@@ -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";
index 4be0899f0b44c4b0c84ed0de0ed6b153a198d5db..6fe8c9306262be884532227d0f586b61d7f4a06e 100644 (file)
@@ -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()
index e76c817d444b1541a6b749424d516b4cf62ef00c..9473dfe33566a35a15d8c6c29ab420600f687bc0 100644 (file)
@@ -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?