--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.platform.ws;
+
+import org.sonar.api.platform.Server;
+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.text.JsonWriter;
+import org.sonar.server.db.migrations.DatabaseMigration;
+import org.sonar.server.platform.Platform;
+
+import com.google.common.io.Resources;
+
+/**
+ * Implementation of the {@code status} action for the System WebService.
+ */
+public class SystemStatusWsAction implements SystemWsAction {
+
+ private final Server server;
+ private final DatabaseMigration databaseMigration;
+ private final Platform platform;
+
+ public SystemStatusWsAction(Server server, DatabaseMigration databaseMigration, Platform platform) {
+ this.server = server;
+ this.databaseMigration = databaseMigration;
+ this.platform = platform;
+ }
+
+ @Override
+ public void define(WebService.NewController controller) {
+ controller.createAction("status")
+ .setDescription("Get the server status:" +
+ "<ul>" +
+ "<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>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>")
+ .setSince("5.2")
+ .setResponseExample(Resources.getResource(this.getClass(), "example-status.json"))
+ .setHandler(this);
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ JsonWriter json = response.newJsonWriter();
+ writeJson(json);
+ json.close();
+ }
+
+ private void writeJson(JsonWriter json) {
+ Status status = computeStatus();
+
+ json.beginObject();
+ json.prop("id", server.getId());
+ json.prop("version", server.getVersion());
+ json.prop("status", status.toString());
+ json.endObject();
+ }
+
+ private Status computeStatus() {
+ if (!isConnectedToDB()) {
+ return Status.DOWN;
+ }
+
+ Platform.Status platformStatus = platform.status();
+ switch (platformStatus) {
+ case BOOTING:
+ // can not happen since there can not even exist an instance of the current class
+ // unless the Platform's status is UP or SAFEMODE
+ return Status.DOWN;
+ case UP:
+ return Status.UP;
+ case SAFEMODE:
+ return computeFromDbMigrationStatus();
+ default:
+ throw new IllegalArgumentException("Unsupported Platform.Status " + platformStatus);
+ }
+ }
+
+ private boolean isConnectedToDB() {
+ // TODO check DB connection is up
+ return true;
+ }
+
+ private Status computeFromDbMigrationStatus() {
+ DatabaseMigration.Status databaseMigrationStatus = databaseMigration.status();
+ switch (databaseMigrationStatus) {
+ case NONE:
+ return Status.DB_MIGRATION_NEEDED;
+ case RUNNING:
+ return Status.DB_MIGRATION_RUNNING;
+ case FAILED:
+ return Status.DOWN;
+ case SUCCEEDED:
+ // status of Platform is supposed to be UP _before_ DatabaseMigration status becomes UP too
+ // so, in theory, this case can not happen
+ return Status.UP;
+ default:
+ throw new IllegalArgumentException("Unsupported DatabaseMigration.Status " + databaseMigrationStatus);
+ }
+ }
+
+ private enum Status {
+ UP, DOWN, DB_MIGRATION_NEEDED, DB_MIGRATION_RUNNING
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.platform.ws;
+
+import java.io.File;
+import java.util.Date;
+import java.util.Set;
+import org.junit.Test;
+import org.sonar.api.platform.Server;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.db.migrations.DatabaseMigration;
+import org.sonar.server.platform.Platform;
+import org.sonar.server.ws.WsTester;
+
+import static com.google.common.base.Predicates.in;
+import static com.google.common.base.Predicates.not;
+import static com.google.common.collect.ImmutableSet.of;
+import static com.google.common.collect.Iterables.filter;
+import static java.util.Arrays.asList;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.test.JsonAssert.assertJson;
+
+public class SystemStatusWsActionTest {
+ private static final String DUMMY_CONTROLLER_KEY = "dummy";
+
+ private static final String SERVER_ID = "20150504120436";
+ private static final String SERVER_VERSION = "5.1";
+ private static final String STATUS_UP = "UP";
+ 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 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 static Server server = new Dummy51Server();
+ private DatabaseMigration databaseMigration = mock(DatabaseMigration.class);
+ private Platform platform = mock(Platform.class);
+ private Request request = mock(Request.class);
+ private SystemStatusWsAction underTest = new SystemStatusWsAction(server, databaseMigration, platform);
+
+ @Test
+ public void action_status_is_defined() throws Exception {
+ WsTester wsTester = new WsTester();
+ WebService.NewController newController = wsTester.context().createController(DUMMY_CONTROLLER_KEY);
+
+ underTest.define(newController);
+ newController.done();
+
+ WebService.Controller controller = wsTester.controller(DUMMY_CONTROLLER_KEY);
+ assertThat(controller.actions()).extracting("key").containsExactly("status");
+
+ WebService.Action action = controller.actions().iterator().next();
+ assertThat(action.isPost()).isFalse();
+ assertThat(action.description()).isNotEmpty();
+ assertThat(action.responseExample()).isNotNull();
+
+ assertThat(action.params()).isEmpty();
+ }
+
+ @Test
+ public void verify_example() throws Exception {
+ when(platform.status()).thenReturn(Platform.Status.UP);
+
+ WsTester.TestResponse response = new WsTester.TestResponse();
+ underTest.handle(request, response);
+
+ assertJson(response.outputAsString()).isSimilarTo(getClass().getResource("example-status.json"));
+ }
+
+ @Test
+ public void status_is_UP_if_platform_is_UP_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_DOWN_if_platform_is_BOOTING_whatever_databaseMigration_status_is() throws Exception {
+ for (DatabaseMigration.Status databaseMigrationStatus : DatabaseMigration.Status.values()) {
+ verifyStatus(Platform.Status.BOOTING, databaseMigrationStatus, STATUS_DOWN);
+ }
+ }
+
+ @Test
+ public void status_is_DB_MIGRATION_NEEDED_if_platform_is_SAFEMODE_and_databaseMigration_is_NONE() throws Exception {
+ verifyStatus(Platform.Status.SAFEMODE, DatabaseMigration.Status.NONE, STATUS_MIGRATION_NEEDED);
+ }
+
+ @Test
+ public void status_is_DB_MIGRATION_RUNNING_if_platform_is_SAFEMODE_and_databaseMigration_is_RUNNING() throws Exception {
+ verifyStatus(Platform.Status.SAFEMODE, DatabaseMigration.Status.RUNNING, STATUS_MIGRATION_RUNNING);
+ }
+
+ @Test
+ public void status_is_UP_if_platform_is_SAFEMODE_and_databaseMigration_is_SUCCEEDED() throws Exception {
+ verifyStatus(Platform.Status.SAFEMODE, DatabaseMigration.Status.SUCCEEDED, STATUS_UP);
+ }
+
+ @Test
+ public void status_is_DOWN_if_platform_is_SAFEMODE_and_databaseMigration_is_FAILED() throws Exception {
+ verifyStatus(Platform.Status.SAFEMODE, DatabaseMigration.Status.FAILED, STATUS_DOWN);
+ }
+
+ @Test
+ public void safety_test_for_new_platform_status() throws Exception {
+ for (Platform.Status platformStatus : filter(asList(Platform.Status.values()), not(in(SUPPORTED_PLATFORM_STATUSES)))) {
+ for (DatabaseMigration.Status databaseMigrationStatus : DatabaseMigration.Status.values()) {
+ verifyStatus(platformStatus, databaseMigrationStatus, STATUS_DOWN);
+ }
+ }
+ }
+
+ @Test
+ public void safety_test_for_new_databaseMigration_status_when_platform_is_SAFEMODE() throws Exception {
+ for (DatabaseMigration.Status databaseMigrationStatus : filter(asList(DatabaseMigration.Status.values()), not(in(SUPPORTED_DATABASE_MIGRATION_STATUSES)))) {
+ when(platform.status()).thenReturn(Platform.Status.SAFEMODE);
+ when(databaseMigration.status()).thenReturn(databaseMigrationStatus);
+
+ WsTester.TestResponse response = new WsTester.TestResponse();
+ underTest.handle(request, response);
+ }
+ }
+
+ private void verifyStatus(Platform.Status platformStatus, DatabaseMigration.Status databaseMigrationStatus, String expectedStatus) throws Exception {
+ when(platform.status()).thenReturn(platformStatus);
+ when(databaseMigration.status()).thenReturn(databaseMigrationStatus);
+
+ WsTester.TestResponse response = new WsTester.TestResponse();
+ underTest.handle(request, response);
+
+ assertJson(response.outputAsString()).isSimilarTo("{" +
+ " \"status\": \"" + expectedStatus + "\"\n" +
+ "}");
+ }
+
+ private static class Dummy51Server extends Server {
+ @Override
+ public String getId() {
+ return SERVER_ID;
+ }
+
+ @Override
+ public String getVersion() {
+ return SERVER_VERSION;
+ }
+
+ @Override
+ public Date getStartedAt() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public File getRootDir() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public File getDeployDir() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getContextPath() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getURL() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getPermanentServerId() {
+ throw new UnsupportedOperationException();
+ }
+ }
+}