@@ -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(); | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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; |
@@ -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, |
@@ -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(); |
@@ -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 | |||
} | |||
} |
@@ -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 |
@@ -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()) { |
@@ -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(); |