]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR−7565 add RESTARTING status in /api/system/status
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Mon, 25 Apr 2016 14:08:36 +0000 (16:08 +0200)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Tue, 26 Apr 2016 07:52:49 +0000 (09:52 +0200)
server/sonar-server/src/main/java/org/sonar/server/app/RestartFlagHolder.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/app/RestartFlagHolderImpl.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
server/sonar-server/src/main/java/org/sonar/server/platform/ws/RestartAction.java
server/sonar-server/src/main/java/org/sonar/server/platform/ws/StatusAction.java
server/sonar-server/src/test/java/org/sonar/server/platform/ws/RestartActionTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/ws/StatusActionTest.java
server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java

diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/RestartFlagHolder.java b/server/sonar-server/src/main/java/org/sonar/server/app/RestartFlagHolder.java
new file mode 100644 (file)
index 0000000..cd86825
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * 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.app;
+
+/**
+ * Holds a boolean flag representing the restarting status of the WebServer.
+ * This boolean is {@code false} by default and can safely be changed concurrently using methods {@link #set()} and
+ * {@link #unset()}.
+ */
+public interface RestartFlagHolder {
+  /**
+   * @return whether restarting flag has been set or not.
+   */
+  boolean isRestarting();
+
+  /**
+   * Sets the restarting flag to {@code true}, no matter it already is or not.
+   */
+  void set();
+
+  /**
+   * Sets the restarting flag to {@code false}, no matter it already is or not.
+   */
+  void unset();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/RestartFlagHolderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/app/RestartFlagHolderImpl.java
new file mode 100644 (file)
index 0000000..8b9af93
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * 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.app;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+public class RestartFlagHolderImpl implements RestartFlagHolder {
+  private final AtomicBoolean restarting = new AtomicBoolean(false);
+
+  @Override
+  public boolean isRestarting() {
+    return restarting.get();
+  }
+
+  @Override
+  public void set() {
+    restarting.set(true);
+  }
+
+  @Override
+  public void unset() {
+    restarting.set(false);
+  }
+}
index a801164fd20fb4c340730dd49f532d2cbfcaa2ae..68f4dadbd9100f8b38296ecc236dafd9a40d27b6 100644 (file)
@@ -157,10 +157,12 @@ public class Platform {
     if (!started) {
       return Status.BOOTING;
     }
-    if (levelSafeMode != null && currentLevel == levelSafeMode) {
+    PlatformLevel current = this.currentLevel;
+    PlatformLevel levelSafe = this.levelSafeMode;
+    if (levelSafe != null && current == levelSafe) {
       return Status.SAFEMODE;
     }
-    if (currentLevel == level4) {
+    if (current == level4) {
       return Status.UP;
     }
     return Status.BOOTING;
index a64ef830bf211be03daf60222ecf4a41d4569651..6f2a5b1b6679ff4a9c0a0fbbc73acaefa4e7ac68 100644 (file)
@@ -36,6 +36,7 @@ import org.sonar.db.semaphore.SemaphoresImpl;
 import org.sonar.db.version.DatabaseVersion;
 import org.sonar.db.version.MigrationStepModule;
 import org.sonar.server.app.ProcessCommandWrapperImpl;
+import org.sonar.server.app.RestartFlagHolderImpl;
 import org.sonar.server.db.EmbeddedDatabaseFactory;
 import org.sonar.server.issue.index.IssueIndex;
 import org.sonar.server.platform.DatabaseServerCompatibility;
@@ -70,6 +71,7 @@ public class PlatformLevel1 extends PlatformLevel {
     add(
       SonarQubeVersionFactory.create(System2.INSTANCE),
       ProcessCommandWrapperImpl.class,
+      RestartFlagHolderImpl.class,
       WebServerSettings.class,
       ServerImpl.class,
       UuidFactoryImpl.INSTANCE,
index 69fba19b97548542a6dbb2f0c314daa3c59235b0..58c87ac075046d9bf30d89674ac25708cbb9e919 100644 (file)
@@ -27,6 +27,7 @@ import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.api.web.UserRole;
 import org.sonar.server.app.ProcessCommandWrapper;
+import org.sonar.server.app.RestartFlagHolder;
 import org.sonar.server.platform.Platform;
 import org.sonar.server.user.UserSession;
 
@@ -41,12 +42,14 @@ public class RestartAction implements SystemWsAction {
   private final Settings settings;
   private final Platform platform;
   private final ProcessCommandWrapper processCommandWrapper;
+  private final RestartFlagHolder restartFlagHolder;
 
-  public RestartAction(UserSession userSession, Settings settings, Platform platform, ProcessCommandWrapper processCommandWrapper) {
+  public RestartAction(UserSession userSession, Settings settings, Platform platform, ProcessCommandWrapper processCommandWrapper, RestartFlagHolder restartFlagHolder) {
     this.userSession = userSession;
     this.settings = settings;
     this.platform = platform;
     this.processCommandWrapper = processCommandWrapper;
+    this.restartFlagHolder = restartFlagHolder;
   }
 
   @Override
@@ -65,11 +68,17 @@ public class RestartAction implements SystemWsAction {
   public void handle(Request request, Response response) {
     if (settings.getBoolean("sonar.web.dev")) {
       LOGGER.info("Fast restarting WebServer...");
-      platform.restart();
-      LOGGER.info("WebServer restarted");
+      restartFlagHolder.set();
+      try {
+        platform.restart();
+        LOGGER.info("WebServer restarted");
+      } finally {
+        restartFlagHolder.unset();
+      }
     } else {
       userSession.checkPermission(UserRole.ADMIN);
       LOGGER.info("SonarQube restart requested by {}", userSession.getLogin());
+      restartFlagHolder.set();
       processCommandWrapper.requestSQRestart();
     }
     response.noContent();
index b7b6a99f2cce538f93215c10b7aade4b2feeaab8..57b6c4205539c4aef27586fc500a606934c5a67f 100644 (file)
@@ -31,6 +31,7 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.IsAliveMapper;
 import org.sonar.db.version.DatabaseMigration;
+import org.sonar.server.app.RestartFlagHolder;
 import org.sonar.server.platform.Platform;
 
 /**
@@ -44,12 +45,14 @@ public class StatusAction implements SystemWsAction {
   private final DatabaseMigration databaseMigration;
   private final Platform platform;
   private final DbClient dbClient;
+  private final RestartFlagHolder restartFlagHolder;
 
-  public StatusAction(Server server, DatabaseMigration databaseMigration, Platform platform, DbClient dbClient) {
+  public StatusAction(Server server, DatabaseMigration databaseMigration, Platform platform, DbClient dbClient, RestartFlagHolder restartFlagHolder) {
     this.server = server;
     this.databaseMigration = databaseMigration;
     this.platform = platform;
     this.dbClient = dbClient;
+    this.restartFlagHolder = restartFlagHolder;
   }
 
   @Override
@@ -60,6 +63,8 @@ public class StatusAction implements SystemWsAction {
         "<li>UP: SonarQube instance is up and running</li>" +
         "<li>DOWN: SonarQube instance is up but not running because SQ can not connect to database or " +
         "migration has failed (refer to WS /api/system/migrate_db for details) or some other reason (check logs).</li>" +
+        "<li>RESTARTING: SonarQube instance is still up but a restart has been requested " +
+        "(refer to WS /api/system/restart for details).</li>" +
         "<li>DB_MIGRATION_NEEDED: database migration is required. DB migration can be started using WS /api/system/migrate_db.</li>" +
         "<li>DB_MIGRATION_RUNNING: DB migration is running (refer to WS /api/system/migrate_db for details)</li>" +
         "</ul>")
@@ -97,7 +102,7 @@ public class StatusAction implements SystemWsAction {
         // unless the Platform's status is UP or SAFEMODE
         return Status.DOWN;
       case UP:
-        return Status.UP;
+        return restartFlagHolder.isRestarting() ? Status.RESTARTING : Status.UP;
       case SAFEMODE:
         return computeFromDbMigrationStatus();
       default:
@@ -133,7 +138,7 @@ public class StatusAction implements SystemWsAction {
   }
 
   private enum Status {
-    UP, DOWN, DB_MIGRATION_NEEDED, DB_MIGRATION_RUNNING
+    UP, DOWN, DB_MIGRATION_NEEDED, DB_MIGRATION_RUNNING, RESTARTING
   }
 
 }
index 8d08facfdf56ddfb78e96d8b64d6cba1584cd45d..c45b101dd55e2a555232aead1e4ea83a2b65fe05 100644 (file)
@@ -22,11 +22,14 @@ package org.sonar.server.platform.ws;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
 import org.sonar.api.config.Settings;
 import org.sonar.api.utils.log.LogTester;
 import org.sonar.api.utils.log.LoggerLevel;
 import org.sonar.api.web.UserRole;
 import org.sonar.server.app.ProcessCommandWrapper;
+import org.sonar.server.app.RestartFlagHolder;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.platform.Platform;
 import org.sonar.server.tester.UserSessionRule;
@@ -34,8 +37,8 @@ import org.sonar.server.ws.WsActionTester;
 import org.sonar.server.ws.WsTester;
 
 import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
 
 public class RestartActionTest {
   @Rule
@@ -48,7 +51,10 @@ public class RestartActionTest {
   private Settings settings = new Settings();
   private Platform platform = mock(Platform.class);
   private ProcessCommandWrapper processCommandWrapper = mock(ProcessCommandWrapper.class);
-  private RestartAction sut = new RestartAction(userSessionRule, settings, platform, processCommandWrapper);
+  private RestartFlagHolder restartFlagHolder = mock(RestartFlagHolder.class);
+  private RestartAction sut = new RestartAction(userSessionRule, settings, platform, processCommandWrapper, restartFlagHolder);
+  private InOrder inOrder = Mockito.inOrder(platform, restartFlagHolder, processCommandWrapper);
+
   private WsActionTester actionTester = new WsActionTester(sut);
 
   @Test
@@ -59,7 +65,30 @@ public class RestartActionTest {
 
     WsTester tester = new WsTester(ws);
     tester.newPostRequest("api/system", "restart").execute();
-    verify(platform).restart();
+    InOrder inOrder = Mockito.inOrder(platform, restartFlagHolder);
+    inOrder.verify(restartFlagHolder).set();
+    inOrder.verify(platform).restart();
+    inOrder.verify(restartFlagHolder).unset();
+  }
+
+  @Test
+  public void restart_flag_is_unset_in_dev_mode_even_if_restart_fails() throws Exception {
+    settings.setProperty("sonar.web.dev", true);
+    RuntimeException toBeThrown = new RuntimeException("simulating platform.restart() failed");
+    doThrow(toBeThrown).when(platform).restart();
+
+    SystemWs ws = new SystemWs(sut);
+
+    WsTester tester = new WsTester(ws);
+    try {
+      tester.newPostRequest("api/system", "restart").execute();
+    } catch (RuntimeException e) {
+      assertThat(e).isSameAs(toBeThrown);
+    } finally {
+      inOrder.verify(restartFlagHolder).set();
+      inOrder.verify(platform).restart();
+      inOrder.verify(restartFlagHolder).unset();
+    }
   }
 
   @Test
@@ -75,7 +104,8 @@ public class RestartActionTest {
 
     actionTester.newRequest().execute();
 
-    verify(processCommandWrapper).requestSQRestart();
+    inOrder.verify(restartFlagHolder).set();
+    inOrder.verify(processCommandWrapper).requestSQRestart();
   }
 
   @Test
index 74ef58d2ad4d1f3c9f3e05d6b5273cfb1b7e4fb8..2e0960a2d81ec4b36ee33a861c9f795a75cfe9e5 100644 (file)
@@ -31,6 +31,8 @@ import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.IsAliveMapper;
 import org.sonar.db.version.DatabaseMigration;
+import org.sonar.server.app.RestartFlagHolder;
+import org.sonar.server.app.RestartFlagHolderImpl;
 import org.sonar.server.platform.Platform;
 import org.sonar.server.ws.WsTester;
 
@@ -54,6 +56,7 @@ public class StatusActionTest {
   private static final String STATUS_DOWN = "DOWN";
   private static final String STATUS_MIGRATION_NEEDED = "DB_MIGRATION_NEEDED";
   private static final String STATUS_MIGRATION_RUNNING = "DB_MIGRATION_RUNNING";
+  private static final String STATUS_RESTARTING = "RESTARTING";
   private static final Set<DatabaseMigration.Status> SUPPORTED_DATABASE_MIGRATION_STATUSES = of(DatabaseMigration.Status.FAILED, DatabaseMigration.Status.NONE,
     DatabaseMigration.Status.SUCCEEDED, DatabaseMigration.Status.RUNNING);
   private static final Set<Platform.Status> SUPPORTED_PLATFORM_STATUSES = of(Platform.Status.BOOTING, Platform.Status.SAFEMODE, Platform.Status.UP);
@@ -64,7 +67,8 @@ public class StatusActionTest {
   private DbClient dbClient = mock(DbClient.class);
   private DbSession dbSession = mock(DbSession.class);
   private IsAliveMapper isAliveMapper = mock(IsAliveMapper.class);
-  private StatusAction underTest = new StatusAction(server, databaseMigration, platform, dbClient);
+  private RestartFlagHolder restartFlagHolder = new RestartFlagHolderImpl();
+  private StatusAction underTest = new StatusAction(server, databaseMigration, platform, dbClient, restartFlagHolder);
 
   private Request request = mock(Request.class);
 
@@ -97,6 +101,7 @@ public class StatusActionTest {
   public void verify_example() throws Exception {
     when(isAliveMapper.isAlive()).thenReturn(IsAliveMapper.IS_ALIVE_RETURNED_VALUE);
     when(platform.status()).thenReturn(Platform.Status.UP);
+    restartFlagHolder.unset();
 
     WsTester.TestResponse response = new WsTester.TestResponse();
     underTest.handle(request, response);
@@ -105,12 +110,21 @@ public class StatusActionTest {
   }
 
   @Test
-  public void status_is_UP_if_platform_is_UP_whatever_databaseMigration_status_is() throws Exception {
+  public void status_is_UP_if_platform_is_UP_and_restartFlag_is_false_whatever_databaseMigration_status_is() throws Exception {
     for (DatabaseMigration.Status databaseMigrationStatus : DatabaseMigration.Status.values()) {
       verifyStatus(Platform.Status.UP, databaseMigrationStatus, STATUS_UP);
     }
   }
 
+  @Test
+  public void status_is_RESTARTING_if_platform_is_UP_and_restartFlag_is_true_whatever_databaseMigration_status_is() throws Exception {
+    restartFlagHolder.set();
+
+    for (DatabaseMigration.Status databaseMigrationStatus : DatabaseMigration.Status.values()) {
+      verifyStatus(Platform.Status.UP, databaseMigrationStatus, STATUS_RESTARTING);
+    }
+  }
+
   @Test
   public void status_is_DOWN_if_platform_is_BOOTING_whatever_databaseMigration_status_is() throws Exception {
     for (DatabaseMigration.Status databaseMigrationStatus : DatabaseMigration.Status.values()) {
index adda7539e6bdc07965e0d0f9c07cfb46f438c0a8..112df0694a3ca2767a4652e397363b84a58f527d 100644 (file)
@@ -23,6 +23,7 @@ import org.junit.Test;
 import org.sonar.api.config.Settings;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.server.app.ProcessCommandWrapper;
+import org.sonar.server.app.RestartFlagHolder;
 import org.sonar.server.platform.Platform;
 import org.sonar.server.platform.monitoring.ProcessSystemInfoClient;
 import org.sonar.server.tester.AnonymousMockUserSession;
@@ -37,7 +38,7 @@ public class SystemWsTest {
 
   @Test
   public void define() {
-    RestartAction action1 = new RestartAction(mock(UserSession.class), mock(Settings.class), mock(Platform.class), mock(ProcessCommandWrapper.class));
+    RestartAction action1 = new RestartAction(mock(UserSession.class), mock(Settings.class), mock(Platform.class), mock(ProcessCommandWrapper.class), mock(RestartFlagHolder.class));
     InfoAction action2 = new InfoAction(new AnonymousMockUserSession(), processSystemInfoClient);
     SystemWs ws = new SystemWs(action1, action2);
     WebService.Context context = new WebService.Context();