--- /dev/null
+/*
+ * 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();
+}
--- /dev/null
+/*
+ * 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);
+ }
+}
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;
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;
add(
SonarQubeVersionFactory.create(System2.INSTANCE),
ProcessCommandWrapperImpl.class,
+ RestartFlagHolderImpl.class,
WebServerSettings.class,
ServerImpl.class,
UuidFactoryImpl.INSTANCE,
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;
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
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();
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;
/**
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
"<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>")
// 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:
}
private enum Status {
- UP, DOWN, DB_MIGRATION_NEEDED, DB_MIGRATION_RUNNING
+ UP, DOWN, DB_MIGRATION_NEEDED, DB_MIGRATION_RUNNING, RESTARTING
}
}
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;
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
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
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
actionTester.newRequest().execute();
- verify(processCommandWrapper).requestSQRestart();
+ inOrder.verify(restartFlagHolder).set();
+ inOrder.verify(processCommandWrapper).requestSQRestart();
}
@Test
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;
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);
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);
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);
}
@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()) {
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;
@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();