From 2b5701d42025b8cce2d192b494f61e91e591e591 Mon Sep 17 00:00:00 2001 From: =?utf8?q?S=C3=A9bastien=20Lesaint?= Date: Thu, 7 Jan 2016 12:18:10 +0100 Subject: [PATCH] SONAR-7168 /api/system/restart now restarts SQ in production mode --- .../server/platform/ws/RestartAction.java | 48 +++++++++--- .../server/platform/ws/RestartActionTest.java | 75 ++++++++++++++++--- .../server/platform/ws/SystemWsTest.java | 3 +- 3 files changed, 103 insertions(+), 23 deletions(-) diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/RestartAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/RestartAction.java index 5fa9bc0521d..d2caac6a8eb 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/RestartAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/RestartAction.java @@ -19,14 +19,20 @@ */ package org.sonar.server.platform.ws; +import java.io.File; import org.sonar.api.config.Settings; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; -import org.sonar.server.exceptions.ForbiddenException; +import org.sonar.api.web.UserRole; +import org.sonar.process.DefaultProcessCommands; +import org.sonar.process.ProcessCommands; import org.sonar.server.platform.Platform; +import org.sonar.server.user.UserSession; + +import static com.google.common.base.Preconditions.checkArgument; /** * Implementation of the {@code restart} action for the System WebService. @@ -34,11 +40,15 @@ import org.sonar.server.platform.Platform; public class RestartAction implements SystemWsAction { private static final Logger LOGGER = Loggers.get(RestartAction.class); + private static final String PROPERTY_SHARED_PATH = "process.sharedDir"; + private static final String PROPERTY_PROCESS_INDEX = "process.index"; + private final UserSession userSession; private final Settings settings; private final Platform platform; - public RestartAction(Settings settings, Platform platform) { + public RestartAction(UserSession userSession, Settings settings, Platform platform) { + this.userSession = userSession; this.settings = settings; this.platform = platform; } @@ -46,8 +56,10 @@ public class RestartAction implements SystemWsAction { @Override public void define(WebService.NewController controller) { controller.createAction("restart") - .setDescription("Restart server. Available only on development mode (sonar.web.dev=true). " + - "Ruby on Rails extensions are not reloaded.") + .setDescription("Restart server. " + + "In development mode (sonar.web.dev=true), performs a partial and quick restart of only the web server where " + + "Ruby on Rails extensions are not reloaded. " + + "In Production mode, require system administration permission and fully restart web server and Elastic Search processes.") .setSince("4.3") .setPost(true) .setHandler(this); @@ -55,14 +67,30 @@ public class RestartAction implements SystemWsAction { @Override public void handle(Request request, Response response) { - if (!settings.getBoolean("sonar.web.dev")) { - throw new ForbiddenException("Webservice available only in dev mode"); + if (settings.getBoolean("sonar.web.dev")) { + LOGGER.info("Restart server"); + platform.restart(); + LOGGER.info("Server restarted"); + } else { + LOGGER.info("Requesting SonarQube restart"); + userSession.checkPermission(UserRole.ADMIN); + ProcessCommands commands = new DefaultProcessCommands( + nonNullValueAsFile(PROPERTY_SHARED_PATH), nonNullAsInt(PROPERTY_PROCESS_INDEX)); + commands.askForRestart(); } - - LOGGER.info("Restart server"); - platform.restart(); - LOGGER.info("Server restarted"); response.noContent(); } + private int nonNullAsInt(String key) { + String s = settings.getString(key); + checkArgument(s != null, "Property %s is not set", key); + return Integer.parseInt(s); + } + + public File nonNullValueAsFile(String key) { + String s = settings.getString(key); + checkArgument(s != null, "Property %s is not set", key); + return new File(s); + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/RestartActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/RestartActionTest.java index 40ee840fe85..968f82aa2d6 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/RestartActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/RestartActionTest.java @@ -19,20 +19,39 @@ */ package org.sonar.server.platform.ws; +import java.io.File; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; import org.sonar.api.config.Settings; +import org.sonar.api.web.UserRole; +import org.sonar.process.DefaultProcessCommands; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.platform.Platform; +import org.sonar.server.tester.UserSessionRule; +import org.sonar.server.ws.WsActionTester; import org.sonar.server.ws.WsTester; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.*; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; public class RestartActionTest { + private static final int PROCESS_NUMBER = 1; + + @Rule + public UserSessionRule userSessionRule = UserSessionRule.standalone(); + @Rule + public ExpectedException expectedException = ExpectedException.none(); + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + Settings settings = new Settings(); Platform platform = mock(Platform.class); - RestartAction sut = new RestartAction(settings, platform); + RestartAction sut = new RestartAction(userSessionRule, settings, platform); + WsActionTester actionTester = new WsActionTester(sut); @Test public void restart_if_dev_mode() throws Exception { @@ -46,15 +65,47 @@ public class RestartActionTest { } @Test - public void fail_if_production_mode() throws Exception { - SystemWs ws = new SystemWs(sut); + public void requires_admin_permission_if_production_mode() { + expectedException.expect(ForbiddenException.class); - WsTester tester = new WsTester(ws); - try { - tester.newPostRequest("api/system", "restart").execute(); - fail(); - } catch (ForbiddenException e) { - verifyZeroInteractions(platform); - } + actionTester.newRequest().execute(); } + + @Test + public void fail_process_sharedDir_property_not_set_in_production_mode() throws Exception { + userSessionRule.login().setGlobalPermissions(UserRole.ADMIN); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Property process.sharedDir is not set"); + + actionTester.newRequest().execute(); + } + + @Test + public void fail_process_index_property_not_set_in_production_mode() throws Exception { + userSessionRule.login().setGlobalPermissions(UserRole.ADMIN); + settings.setProperty("process.sharedDir", temp.newFolder().getAbsolutePath()); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Property process.index is not set"); + + actionTester.newRequest().execute(); + } + + @Test + public void askForRestart_in_shared_memory_in_production_mode() throws Exception { + int processNumber = 2; + File tempFolder = temp.newFolder().getAbsoluteFile(); + + userSessionRule.login().setGlobalPermissions(UserRole.ADMIN); + settings.setProperty("process.sharedDir", tempFolder.getAbsolutePath()); + settings.setProperty("process.index", processNumber); + + DefaultProcessCommands processCommands = new DefaultProcessCommands(tempFolder, processNumber); + + actionTester.newRequest().execute(); + + assertThat(processCommands.askedForRestart()).isTrue(); + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java index db57cce361e..34aa22df9a7 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SystemWsTest.java @@ -24,6 +24,7 @@ import org.sonar.api.config.Settings; import org.sonar.api.server.ws.WebService; import org.sonar.server.platform.Platform; import org.sonar.server.tester.AnonymousMockUserSession; +import org.sonar.server.user.UserSession; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -32,7 +33,7 @@ public class SystemWsTest { @Test public void define() { - RestartAction action1 = new RestartAction(mock(Settings.class), mock(Platform.class)); + RestartAction action1 = new RestartAction(mock(UserSession.class), mock(Settings.class), mock(Platform.class)); InfoAction action2 = new InfoAction(new AnonymousMockUserSession()); SystemWs ws = new SystemWs(action1, action2); WebService.Context context = new WebService.Context(); -- 2.39.5