From b3b8a174238de179380ff3af762a767063d9ae67 Mon Sep 17 00:00:00 2001
From: =?utf8?q?S=C3=A9bastien=20Lesaint?=
Date: Tue, 14 Apr 2015 16:43:59 +0200
Subject: [PATCH] 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
---
.../org/sonar/server/platform/Platform.java | 131 +++++++++++++-----
.../server/platform/ServerComponents.java | 42 +++++-
.../org/sonar/server/tester/ServerTester.java | 12 ++
.../app/controllers/api/java_ws_controller.rb | 4 +
.../webapp/WEB-INF/lib/database_version.rb | 2 +-
5 files changed, 154 insertions(+), 37 deletions(-)
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:
+ *
+ * - WS to query status of the server and upgrade the DB and their dependencies
+ * - WS listing Webservice to allow user to list WebServices available in safemode
+ *
+ *
+ */
+ public Collection