aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2017-08-23 16:26:39 +0200
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2017-09-13 15:50:47 +0200
commiteb6fa5835db8dc3fdc55d75bb13c70464a3e7305 (patch)
treea62c10fcca001a40af41c70650f9e38ff1536630
parentda817d1a7f8511c1ee6e7a233df16c577dd961d7 (diff)
downloadsonarqube-eb6fa5835db8dc3fdc55d75bb13c70464a3e7305.tar.gz
sonarqube-eb6fa5835db8dc3fdc55d75bb13c70464a3e7305.zip
SONAR-9739 add WS api/system/health
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/DbConnectionCheck.java59
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/EsStatusCheck.java60
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/Health.java136
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/HealthCheck.java24
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/HealthChecker.java27
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/HealthCheckerImpl.java77
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/WebServerSafemodeCheck.java39
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/WebServerStatusCheck.java59
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/package-info.java23
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java2
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/AbstractHealthAction.java68
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthAction.java39
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthActionModule.java41
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/SafeModeHealthAction.java33
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/SafeModeHealthActionModule.java40
-rw-r--r--server/sonar-server/src/main/resources/org/sonar/server/platform/ws/example-health.json8
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/health/DbConnectionCheckTest.java81
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/health/EsStatusCheckTest.java42
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/health/HealthCheckerImplTest.java126
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/health/HealthTest.java184
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/health/WebServerSafemodeCheckTest.java37
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/health/WebServerStatusCheckTest.java96
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ws/AbstractHealthActionTestSupport.java76
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ws/HealthActionModuleTest.java69
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ws/HealthActionTest.java93
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ws/SafeModeHealthActionModuleTest.java70
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ws/SafeModeHealthActionTest.java47
-rw-r--r--sonar-ws/src/main/protobuf/ws-system.proto41
29 files changed, 1700 insertions, 1 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/DbConnectionCheck.java b/server/sonar-server/src/main/java/org/sonar/server/health/DbConnectionCheck.java
new file mode 100644
index 00000000000..7d43d29f0ae
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/DbConnectionCheck.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.IsAliveMapper;
+
+import static org.sonar.server.health.Health.newHealthCheckBuilder;
+
+/**
+ * Checks Web Server can connect to the Database.
+ */
+public class DbConnectionCheck implements HealthCheck {
+ private static final Logger LOGGER = Loggers.get(DbConnectionCheck.class);
+ private static final Health RED_HEALTH = newHealthCheckBuilder().setStatus(Health.Status.RED).addCause("Can't connect to DB").build();
+
+ private final DbClient dbClient;
+
+ public DbConnectionCheck(DbClient dbClient) {
+ this.dbClient = dbClient;
+ }
+
+ @Override
+ public Health check() {
+ if (isConnectedToDB()) {
+ return Health.GREEN;
+ }
+ return RED_HEALTH;
+ }
+
+ private boolean isConnectedToDB() {
+ try (DbSession dbSession = dbClient.openSession(false)) {
+ return dbSession.getMapper(IsAliveMapper.class).isAlive() == IsAliveMapper.IS_ALIVE_RETURNED_VALUE;
+ } catch (RuntimeException e) {
+ LOGGER.trace("DB connection is down", e);
+ return false;
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/EsStatusCheck.java b/server/sonar-server/src/main/java/org/sonar/server/health/EsStatusCheck.java
new file mode 100644
index 00000000000..cdf086082c9
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/EsStatusCheck.java
@@ -0,0 +1,60 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+import org.elasticsearch.cluster.health.ClusterHealthStatus;
+import org.sonar.server.es.EsClient;
+
+import static org.sonar.server.health.Health.newHealthCheckBuilder;
+
+/**
+ * Checks the ElasticSearch cluster status.
+ */
+public class EsStatusCheck implements HealthCheck {
+ private static final Health YELLOW_HEALTH = newHealthCheckBuilder()
+ .setStatus(Health.Status.YELLOW)
+ .addCause("Elasticsearch status is YELLOW")
+ .build();
+ private static final Health RED_HEALTH = newHealthCheckBuilder()
+ .setStatus(Health.Status.RED)
+ .addCause("Elasticsearch status is RED")
+ .build();
+
+ private final EsClient esClient;
+
+ public EsStatusCheck(EsClient esClient) {
+ this.esClient = esClient;
+ }
+
+ @Override
+ public Health check() {
+ ClusterHealthStatus esStatus = esClient.prepareClusterStats().get().getStatus();
+ switch (esStatus) {
+ case GREEN:
+ return Health.GREEN;
+ case YELLOW:
+ return YELLOW_HEALTH;
+ case RED:
+ return RED_HEALTH;
+ default:
+ throw new IllegalArgumentException("Unsupported Elasticsearch status " + esStatus);
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/Health.java b/server/sonar-server/src/main/java/org/sonar/server/health/Health.java
new file mode 100644
index 00000000000..065b64a5771
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/Health.java
@@ -0,0 +1,136 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static java.util.Objects.requireNonNull;
+
+public class Health {
+ /**
+ * The GREEN status without any cause as a constant, for convenience and optimisation.
+ */
+ static final Health GREEN = newHealthCheckBuilder()
+ .setStatus(Status.GREEN)
+ .build();
+
+ private final Status status;
+ private final Set<String> causes;
+
+ public Health(Builder builder) {
+ this.status = builder.status;
+ this.causes = ImmutableSet.copyOf(builder.causes);
+ }
+
+ public Status getStatus() {
+ return status;
+ }
+
+ public Set<String> getCauses() {
+ return causes;
+ }
+
+ public static Builder newHealthCheckBuilder() {
+ return new Builder();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ Health health = (Health) o;
+ return status == health.status &&
+ Objects.equals(causes, health.causes);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(status, causes);
+ }
+
+ @Override
+ public String toString() {
+ return "Health{" + status +
+ ", causes=" + causes +
+ '}';
+ }
+
+ /**
+ * Builder of {@link Health} which supports being reused for optimization.
+ */
+ public static class Builder {
+ private Status status;
+ private Set<String> causes = new HashSet<>(0);
+
+ private Builder() {
+ // use static factory method
+ }
+
+ public Builder clear() {
+ this.status = null;
+ this.causes.clear();
+ return this;
+ }
+
+ public Builder setStatus(Status status) {
+ this.status = checkStatus(status);
+ return this;
+ }
+
+ public Builder addCause(String cause) {
+ requireNonNull(cause, "cause can't be null");
+ checkArgument(!cause.trim().isEmpty(), "cause can't be empty");
+ causes.add(cause);
+ return this;
+ }
+
+ public Health build() {
+ checkStatus(this.status);
+ return new Health(this);
+ }
+
+ private static Status checkStatus(Status status) {
+ return requireNonNull(status, "status can't be null");
+ }
+ }
+
+ public enum Status {
+ /**
+ * Fully working
+ */
+ GREEN,
+ /**
+ * Yellow: Working but something must be fixed to make SQ fully operational
+ */
+ YELLOW,
+ /**
+ * Red: Not working
+ */
+ RED
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/HealthCheck.java b/server/sonar-server/src/main/java/org/sonar/server/health/HealthCheck.java
new file mode 100644
index 00000000000..f52f9225003
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/HealthCheck.java
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+public interface HealthCheck {
+ Health check();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/HealthChecker.java b/server/sonar-server/src/main/java/org/sonar/server/health/HealthChecker.java
new file mode 100644
index 00000000000..48d1f94a273
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/HealthChecker.java
@@ -0,0 +1,27 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+public interface HealthChecker {
+ /**
+ * Perform a check of the health of SonarQube, either as a standalone node or as a cluster.
+ */
+ Health check();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/HealthCheckerImpl.java b/server/sonar-server/src/main/java/org/sonar/server/health/HealthCheckerImpl.java
new file mode 100644
index 00000000000..e45e8cd599c
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/HealthCheckerImpl.java
@@ -0,0 +1,77 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BinaryOperator;
+import java.util.stream.Stream;
+
+import static org.sonar.server.health.Health.newHealthCheckBuilder;
+
+/**
+ * Implementation of {@link HealthChecker} that executes implementations of {@link HealthCheck} in the container
+ * and aggregates their results.
+ */
+public class HealthCheckerImpl implements HealthChecker {
+ private final List<HealthCheck> healthChecks;
+
+ public HealthCheckerImpl(HealthCheck... healthChecks) {
+ this.healthChecks = Arrays.asList(healthChecks);
+ }
+
+ @Override
+ public Health check() {
+ return healthChecks.stream().map(HealthCheck::check)
+ .reduce(Health.GREEN, HealthReducer.INSTANCE);
+ }
+
+ private enum HealthReducer implements BinaryOperator<Health> {
+ INSTANCE;
+
+ /**
+ * According to Javadoc, {@link BinaryOperator} used in method
+ * {@link java.util.stream.Stream#reduce(Object, BinaryOperator)} is supposed to be stateless.
+ *
+ * But as we are sure this {@link BinaryOperator} won't be used on a Stream with {@link Stream#parallel()}
+ * feature on, we allow ourselves this optimisation.
+ */
+ private final Health.Builder builder = newHealthCheckBuilder();
+
+ @Override
+ public Health apply(Health left, Health right) {
+ builder.clear();
+ builder.setStatus(worseOf(left.getStatus(), right.getStatus()));
+ left.getCauses().forEach(builder::addCause);
+ right.getCauses().forEach(builder::addCause);
+ return builder.build();
+ }
+
+ private static Health.Status worseOf(Health.Status left, Health.Status right) {
+ if (left == right) {
+ return left;
+ }
+ if (left.ordinal() > right.ordinal()) {
+ return left;
+ }
+ return right;
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/WebServerSafemodeCheck.java b/server/sonar-server/src/main/java/org/sonar/server/health/WebServerSafemodeCheck.java
new file mode 100644
index 00000000000..09a1cdc2e46
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/WebServerSafemodeCheck.java
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+import static org.sonar.server.health.Health.newHealthCheckBuilder;
+
+/**
+ * Checks the running status of the WebServer when the WebServer is in safemode.
+ * Obviously, it statically returns a red health status.
+ */
+public class WebServerSafemodeCheck implements HealthCheck {
+
+ private static final Health RED_HEALTH = newHealthCheckBuilder()
+ .setStatus(Health.Status.RED)
+ .addCause("SonarQube webserver is not up")
+ .build();
+
+ @Override
+ public Health check() {
+ return RED_HEALTH;
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/WebServerStatusCheck.java b/server/sonar-server/src/main/java/org/sonar/server/health/WebServerStatusCheck.java
new file mode 100644
index 00000000000..2146a315d4d
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/WebServerStatusCheck.java
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+import java.util.EnumSet;
+import org.sonar.server.app.RestartFlagHolder;
+import org.sonar.server.platform.Platform;
+import org.sonar.server.platform.db.migration.DatabaseMigrationState;
+
+import static org.sonar.server.health.Health.newHealthCheckBuilder;
+
+/**
+ * Checks the running status of the WebServer when it is not anymore in safemode.
+ */
+public class WebServerStatusCheck implements HealthCheck {
+ private static final EnumSet<DatabaseMigrationState.Status> VALID_DATABASEMIGRATION_STATUSES = EnumSet.of(
+ DatabaseMigrationState.Status.NONE, DatabaseMigrationState.Status.SUCCEEDED);
+
+ private final DatabaseMigrationState migrationState;
+ private final Platform platform;
+ private final RestartFlagHolder restartFlagHolder;
+
+ public WebServerStatusCheck(DatabaseMigrationState migrationState, Platform platform, RestartFlagHolder restartFlagHolder) {
+ this.migrationState = migrationState;
+ this.platform = platform;
+ this.restartFlagHolder = restartFlagHolder;
+ }
+
+ @Override
+ public Health check() {
+ Platform.Status platformStatus = platform.status();
+ if (platformStatus == Platform.Status.UP
+ && VALID_DATABASEMIGRATION_STATUSES.contains(migrationState.getStatus())
+ && !restartFlagHolder.isRestarting()) {
+ return Health.GREEN;
+ }
+ return newHealthCheckBuilder()
+ .setStatus(Health.Status.RED)
+ .addCause("SonarQube webserver is not up")
+ .build();
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/health/package-info.java
new file mode 100644
index 00000000000..ce49c888b43
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/package-info.java
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.health;
+
+import javax.annotation.ParametersAreNonnullByDefault;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index 6786bec8638..bda21231f0a 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -117,6 +117,7 @@ import org.sonar.server.platform.web.WebPagesFilter;
import org.sonar.server.platform.web.requestid.HttpRequestIdModule;
import org.sonar.server.platform.ws.ChangeLogLevelAction;
import org.sonar.server.platform.ws.DbMigrationStatusAction;
+import org.sonar.server.platform.ws.HealthActionModule;
import org.sonar.server.platform.ws.InfoActionModule;
import org.sonar.server.platform.ws.L10nWs;
import org.sonar.server.platform.ws.LogsAction;
@@ -481,7 +482,6 @@ public class PlatformLevel4 extends PlatformLevel {
PingAction.class,
UpgradesAction.class,
StatusAction.class,
- SystemWs.class,
SystemMonitor.class,
SettingsMonitor.class,
SonarQubeMonitor.class,
@@ -493,6 +493,8 @@ public class PlatformLevel4 extends PlatformLevel {
LogsAction.class,
ChangeLogLevelAction.class,
DbMigrationStatusAction.class,
+ HealthActionModule.class,
+ SystemWs.class,
// Server id
ServerIdWsModule.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java
index 23208b88d33..95ba0f135e2 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevelSafeMode.java
@@ -19,6 +19,7 @@
*/
package org.sonar.server.platform.platformlevel;
+import org.sonar.server.platform.ws.SafeModeHealthActionModule;
import org.sonar.server.organization.NoopDefaultOrganizationCache;
import org.sonar.server.platform.ServerImpl;
import org.sonar.server.platform.db.migration.AutoDbMigration;
@@ -55,6 +56,7 @@ public class PlatformLevelSafeMode extends PlatformLevel {
StatusAction.class,
MigrateDbAction.class,
DbMigrationStatusAction.class,
+ SafeModeHealthActionModule.class,
SystemWs.class,
// Listing WS
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/AbstractHealthAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/AbstractHealthAction.java
new file mode 100644
index 00000000000..c0a878b8b5e
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/AbstractHealthAction.java
@@ -0,0 +1,68 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.platform.ws;
+
+import com.google.common.io.Resources;
+import org.sonar.api.server.ws.Request;
+import org.sonar.api.server.ws.Response;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.health.Health;
+import org.sonar.server.health.HealthChecker;
+import org.sonar.server.ws.WsUtils;
+import org.sonarqube.ws.WsSystem;
+
+public abstract class AbstractHealthAction implements SystemWsAction {
+ private final HealthChecker healthChecker;
+
+ public AbstractHealthAction(HealthChecker healthChecker) {
+ this.healthChecker = healthChecker;
+ }
+
+ @Override
+ public void define(WebService.NewController controller) {
+ controller.createAction("health")
+ .setDescription("Provide health status of the current SonarQube instance." +
+ "<p>status: the health status" +
+ " <ul>" +
+ " <li>GREEN: SonarQube is fully operational</li>" +
+ " <li>YELLOW: SonarQube is operational but something must be fixed to be fully operational</li>" +
+ " <li>RED: SonarQube is not operational</li>" +
+ " </ul>" +
+ "</p>")
+ .setSince("6.6")
+ .setResponseExample(Resources.getResource(this.getClass(), "example-health.json"))
+ .setHandler(this);
+ }
+
+ @Override
+ public void handle(Request request, Response response) throws Exception {
+ performAuthenticationChecks();
+
+ Health check = healthChecker.check();
+ WsSystem.HealthResponse.Builder responseBuilder = WsSystem.HealthResponse.newBuilder()
+ .setHealth(WsSystem.Health.valueOf(check.getStatus().name()));
+ WsSystem.Cause.Builder causeBuilder = WsSystem.Cause.newBuilder();
+ check.getCauses().forEach(str -> responseBuilder.addCauses(causeBuilder.clear().setMessage(str).build()));
+
+ WsUtils.writeProtobuf(responseBuilder.build(), request, response);
+ }
+
+ abstract void performAuthenticationChecks();
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthAction.java
new file mode 100644
index 00000000000..ab160d65e57
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthAction.java
@@ -0,0 +1,39 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.platform.ws;
+
+import org.sonar.server.health.HealthChecker;
+import org.sonar.server.user.UserSession;
+
+public class HealthAction extends AbstractHealthAction {
+ private final UserSession userSession;
+
+ public HealthAction(UserSession userSession, HealthChecker healthChecker) {
+ super(healthChecker);
+ this.userSession = userSession;
+ }
+
+ @Override
+ void performAuthenticationChecks() {
+ userSession
+ .checkLoggedIn()
+ .checkIsSystemAdministrator();
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthActionModule.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthActionModule.java
new file mode 100644
index 00000000000..3e9b97aa487
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthActionModule.java
@@ -0,0 +1,41 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.platform.ws;
+
+import org.sonar.core.platform.Module;
+import org.sonar.server.health.DbConnectionCheck;
+import org.sonar.server.health.EsStatusCheck;
+import org.sonar.server.health.HealthCheckerImpl;
+import org.sonar.server.health.WebServerStatusCheck;
+
+public class HealthActionModule extends Module {
+ @Override
+ protected void configureModule() {
+ add(
+ // HealthCheck implementations
+ WebServerStatusCheck.class,
+ DbConnectionCheck.class,
+ EsStatusCheck.class,
+
+ HealthCheckerImpl.class,
+
+ HealthAction.class);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/SafeModeHealthAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/SafeModeHealthAction.java
new file mode 100644
index 00000000000..f3248871603
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/SafeModeHealthAction.java
@@ -0,0 +1,33 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.platform.ws;
+
+import org.sonar.server.health.HealthChecker;
+
+public class SafeModeHealthAction extends AbstractHealthAction {
+ public SafeModeHealthAction(HealthChecker healthChecker) {
+ super(healthChecker);
+ }
+
+ @Override
+ void performAuthenticationChecks() {
+ // no authentication check in safemode
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/SafeModeHealthActionModule.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/SafeModeHealthActionModule.java
new file mode 100644
index 00000000000..a11df47a697
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/SafeModeHealthActionModule.java
@@ -0,0 +1,40 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.platform.ws;
+
+import org.sonar.core.platform.Module;
+import org.sonar.server.health.DbConnectionCheck;
+import org.sonar.server.health.EsStatusCheck;
+import org.sonar.server.health.HealthCheckerImpl;
+import org.sonar.server.health.WebServerSafemodeCheck;
+
+public class SafeModeHealthActionModule extends Module {
+ @Override
+ protected void configureModule() {
+ add(
+ // HealthCheck implementations
+ WebServerSafemodeCheck.class,
+ DbConnectionCheck.class,
+ EsStatusCheck.class,
+
+ HealthCheckerImpl.class,
+ SafeModeHealthAction.class);
+ }
+}
diff --git a/server/sonar-server/src/main/resources/org/sonar/server/platform/ws/example-health.json b/server/sonar-server/src/main/resources/org/sonar/server/platform/ws/example-health.json
new file mode 100644
index 00000000000..9d95994a7b4
--- /dev/null
+++ b/server/sonar-server/src/main/resources/org/sonar/server/platform/ws/example-health.json
@@ -0,0 +1,8 @@
+{
+ "health": "YELLOW",
+ "causes": [
+ {
+ "message": "Elasticsearch status is YELLOW"
+ }
+ ]
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/health/DbConnectionCheckTest.java b/server/sonar-server/src/test/java/org/sonar/server/health/DbConnectionCheckTest.java
new file mode 100644
index 00000000000..41b8c3acd51
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/health/DbConnectionCheckTest.java
@@ -0,0 +1,81 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.sonar.db.DbClient;
+import org.sonar.db.DbSession;
+import org.sonar.db.IsAliveMapper;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class DbConnectionCheckTest {
+ private DbClient dbClient = mock(DbClient.class);
+ private DbSession dbSession = mock(DbSession.class);
+ private IsAliveMapper isAliveMapper = mock(IsAliveMapper.class);
+
+ private DbConnectionCheck underTest = new DbConnectionCheck(dbClient);
+
+ @Before
+ public void wireMocks() {
+ when(dbClient.openSession(anyBoolean())).thenReturn(dbSession);
+ when(dbSession.getMapper(IsAliveMapper.class)).thenReturn(isAliveMapper);
+ }
+
+ @Test
+ public void status_is_GREEN_without_cause_if_isAlive_returns_1() {
+ when(isAliveMapper.isAlive()).thenReturn(1);
+
+ Health health = underTest.check();
+
+ assertThat(health).isEqualTo(Health.GREEN);
+ }
+
+ @Test
+ public void status_is_RED_with_single_cause_if_any_error_occurs_when_checking_DB() throws Exception {
+ when(isAliveMapper.isAlive()).thenThrow(new RuntimeException("simulated runtime exception when querying DB"));
+
+ Health health = underTest.check();
+
+ verifyRedStatus(health);
+ }
+
+ /**
+ * By contract {@link IsAliveMapper#isAlive()} can not return anything but 1. Still we write this test as a
+ * protection against change in this contract.
+ */
+ @Test
+ public void status_is_RED_with_single_cause_if_isAlive_does_not_return_1() throws Exception {
+ when(isAliveMapper.isAlive()).thenReturn(12);
+
+ Health health = underTest.check();
+
+ verifyRedStatus(health);
+ }
+
+ private void verifyRedStatus(Health health) {
+ assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
+ assertThat(health.getCauses()).containsOnly("Can't connect to DB");
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/health/EsStatusCheckTest.java b/server/sonar-server/src/test/java/org/sonar/server/health/EsStatusCheckTest.java
new file mode 100644
index 00000000000..0f8f0183843
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/health/EsStatusCheckTest.java
@@ -0,0 +1,42 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.server.es.EsTester;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EsStatusCheckTest {
+
+ @Rule
+ public EsTester esTester = new EsTester();
+
+ private EsStatusCheck underTest = new EsStatusCheck(esTester.client());
+
+ @Test
+ public void check_returns_GREEN_without_cause_if_ES_cluster_status_is_GREEN() {
+ Health health = underTest.check();
+
+ assertThat(health).isEqualTo(Health.GREEN);
+ }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/health/HealthCheckerImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/health/HealthCheckerImplTest.java
new file mode 100644
index 00000000000..ae91caeee7e
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/health/HealthCheckerImplTest.java
@@ -0,0 +1,126 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import java.util.stream.Stream;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.health.Health.Status.GREEN;
+import static org.sonar.server.health.Health.Status.RED;
+import static org.sonar.server.health.Health.Status.YELLOW;
+
+public class HealthCheckerImplTest {
+
+ private final Random random = new Random();
+
+ @Test
+ public void check_returns_green_status_without_any_cause_when_there_is_no_HealthCheck() {
+ HealthCheckerImpl underTest = new HealthCheckerImpl();
+
+ assertThat(underTest.check()).isEqualTo(Health.GREEN);
+ }
+
+ @Test
+ public void checks_returns_GREEN_status_if_only_GREEN_statuses_returned_by_HealthChecks() {
+ List<Health.Status> statuses = IntStream.range(1, 1 + random.nextInt(20)).mapToObj(i -> GREEN).collect(Collectors.toList());
+ HealthCheckerImpl underTest = newHealthCheckerImpl(statuses.stream());
+
+ assertThat(underTest.check().getStatus())
+ .describedAs("%s should have been computed from %s statuses", GREEN, statuses)
+ .isEqualTo(GREEN);
+ }
+
+ @Test
+ public void checks_returns_YELLOW_status_if_only_GREEN_and_at_least_one_YELLOW_statuses_returned_by_HealthChecks() {
+ List<Health.Status> statuses = new ArrayList<>();
+ Stream.concat(
+ IntStream.range(0, 1 + random.nextInt(20)).mapToObj(i -> YELLOW), // at least 1 YELLOW
+ IntStream.range(0, random.nextInt(20)).mapToObj(i -> GREEN)).forEach(statuses::add); // between 0 and 19 GREEN
+ Collections.shuffle(statuses);
+ HealthCheckerImpl underTest = newHealthCheckerImpl(statuses.stream());
+
+ assertThat(underTest.check().getStatus())
+ .describedAs("%s should have been computed from %s statuses", YELLOW, statuses)
+ .isEqualTo(YELLOW);
+ }
+
+ @Test
+ public void checks_returns_RED_status_if_at_least_one_RED_status_returned_by_HealthChecks() {
+ List<Health.Status> statuses = new ArrayList<>();
+ Stream.of(
+ IntStream.range(0, 1 + random.nextInt(20)).mapToObj(i -> RED), // at least 1 RED
+ IntStream.range(0, random.nextInt(20)).mapToObj(i -> YELLOW), // between 0 and 19 YELLOW
+ IntStream.range(0, random.nextInt(20)).mapToObj(i -> GREEN) // between 0 and 19 GREEN
+ ).flatMap(s -> s)
+ .forEach(statuses::add);
+ Collections.shuffle(statuses);
+ HealthCheckerImpl underTest = newHealthCheckerImpl(statuses.stream());
+
+ assertThat(underTest.check().getStatus())
+ .describedAs("%s should have been computed from %s statuses", RED, statuses)
+ .isEqualTo(RED);
+ }
+
+ @Test
+ public void checks_returns_causes_of_all_HealthChecks_whichever_their_status() {
+ HealthCheck[] healthChecks = IntStream.range(0, 1 + random.nextInt(20))
+ .mapToObj(s -> new StaticHealthCheck(IntStream.range(0, random.nextInt(3)).mapToObj(i -> RandomStringUtils.randomAlphanumeric(3)).toArray(String[]::new)))
+ .map(HealthCheck.class::cast)
+ .toArray(HealthCheck[]::new);
+ String[] expected = Arrays.stream(healthChecks).map(HealthCheck::check).flatMap(s -> s.getCauses().stream()).toArray(String[]::new);
+
+ HealthCheckerImpl underTest = new HealthCheckerImpl(healthChecks);
+
+ assertThat(underTest.check().getCauses()).containsOnly(expected);
+ }
+
+ private HealthCheckerImpl newHealthCheckerImpl(Stream<Health.Status> statuses) {
+ Stream<StaticHealthCheck> staticHealthCheckStream = statuses.map(StaticHealthCheck::new);
+ return new HealthCheckerImpl(staticHealthCheckStream.map(HealthCheck.class::cast).toArray(HealthCheck[]::new));
+ }
+
+ private class StaticHealthCheck implements HealthCheck {
+ private final Health health;
+
+ public StaticHealthCheck(Health.Status status) {
+ this.health = Health.newHealthCheckBuilder().setStatus(status).build();
+ }
+
+ public StaticHealthCheck(String... causes) {
+ Health.Builder builder = Health.newHealthCheckBuilder().setStatus(Health.Status.values()[random.nextInt(3)]);
+ Stream.of(causes).forEach(builder::addCause);
+ this.health = builder.build();
+ }
+
+ @Override
+ public Health check() {
+ return health;
+ }
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/health/HealthTest.java b/server/sonar-server/src/test/java/org/sonar/server/health/HealthTest.java
new file mode 100644
index 00000000000..00874a89d7a
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/health/HealthTest.java
@@ -0,0 +1,184 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+import com.google.common.base.Strings;
+import java.util.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.assertj.core.api.AbstractCharSequenceAssert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.health.Health.newHealthCheckBuilder;
+
+public class HealthTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private final Random random = new Random();
+ private final Health.Status anyStatus = Health.Status.values()[random.nextInt(Health.Status.values().length)];
+ private final Set<String> randomCauses = IntStream.range(0, random.nextInt(5)).mapToObj(s -> randomAlphanumeric(3)).collect(Collectors.toSet());
+
+ @Test
+ public void build_throws_NPE_if_status_is_null() {
+ Health.Builder builder = newHealthCheckBuilder();
+
+ expectStatusNotNullNPE();
+
+ builder.build();
+ }
+
+ @Test
+ public void setStatus_throws_NPE_if_status_is_null() {
+ Health.Builder builder = newHealthCheckBuilder();
+
+ expectStatusNotNullNPE();
+
+ builder.setStatus(null);
+ }
+
+ @Test
+ public void getStatus_returns_status_from_builder() {
+ Health underTest = newHealthCheckBuilder().setStatus(anyStatus).build();
+
+ assertThat(underTest.getStatus()).isEqualTo(anyStatus);
+ }
+
+ @Test
+ public void addCause_throws_NPE_if_arg_is_null() {
+ Health.Builder builder = newHealthCheckBuilder();
+
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("cause can't be null");
+
+ builder.addCause(null);
+ }
+
+ @Test
+ public void addCause_throws_IAE_if_arg_is_empty() {
+ Health.Builder builder = newHealthCheckBuilder();
+
+ expectCauseCannotBeEmptyIAE();
+
+ builder.addCause("");
+ }
+
+ @Test
+ public void addCause_throws_IAE_if_arg_contains_only_spaces() {
+ Health.Builder builder = newHealthCheckBuilder();
+
+ expectCauseCannotBeEmptyIAE();
+
+ builder.addCause(Strings.repeat(" ", 1 + random.nextInt(5)));
+ }
+
+ @Test
+ public void getCause_returns_causes_from_builder() {
+ Health.Builder builder = newHealthCheckBuilder().setStatus(anyStatus);
+ randomCauses.forEach(builder::addCause);
+ Health underTest = builder.build();
+
+ assertThat(underTest.getCauses())
+ .isEqualTo(randomCauses);
+ }
+
+ @Test
+ public void green_constant() {
+ assertThat(Health.GREEN).isEqualTo(newHealthCheckBuilder().setStatus(Health.Status.GREEN).build());
+ }
+
+ @Test
+ public void equals_is_based_on_status_and_causes() {
+ Health.Builder builder1 = newHealthCheckBuilder();
+ Health.Builder builder2 = newHealthCheckBuilder();
+
+ builder1.setStatus(anyStatus);
+ builder2.setStatus(anyStatus);
+ randomCauses.forEach(s -> {
+ builder1.addCause(s);
+ builder2.addCause(s);
+ });
+
+ assertThat(builder1.build())
+ .isEqualTo(builder1.build())
+ .isEqualTo(builder2.build())
+ .isEqualTo(builder2.build());
+ }
+
+ @Test
+ public void not_equals_to_null_nor_other_type() {
+ assertThat(Health.GREEN).isNotEqualTo(null);
+ assertThat(Health.GREEN).isNotEqualTo(new Object());
+ assertThat(Health.GREEN).isNotEqualTo(Health.Status.GREEN);
+ }
+
+ @Test
+ public void hashcode_is_based_on_status_and_causes() {
+ Health.Builder builder1 = newHealthCheckBuilder();
+ Health.Builder builder2 = newHealthCheckBuilder();
+ builder1.setStatus(anyStatus);
+ builder2.setStatus(anyStatus);
+ randomCauses.forEach(s -> {
+ builder1.addCause(s);
+ builder2.addCause(s);
+ });
+
+ assertThat(builder1.build().hashCode())
+ .isEqualTo(builder1.build().hashCode())
+ .isEqualTo(builder2.build().hashCode())
+ .isEqualTo(builder2.build().hashCode());
+ }
+
+ @Test
+ public void verify_toString() {
+ assertThat(Health.GREEN.toString()).isEqualTo("Health{GREEN, causes=[]}");
+ Health.Builder builder = newHealthCheckBuilder().setStatus(anyStatus);
+ randomCauses.forEach(builder::addCause);
+
+ String underTest = builder.build().toString();
+
+ AbstractCharSequenceAssert<?, String> a = assertThat(underTest)
+ .describedAs("toString for status %s and causes %s", anyStatus, randomCauses);
+ if (randomCauses.isEmpty()) {
+ a.isEqualTo("Health{" + anyStatus + ", causes=[]}");
+ } else if (randomCauses.size() == 1) {
+ a.isEqualTo("Health{" + anyStatus + ", causes=[" + randomCauses.iterator().next() + "]}");
+ } else {
+ a.startsWith("Health{" + anyStatus + ", causes=[")
+ .endsWith("]}")
+ .contains(randomCauses);
+ }
+ }
+
+ private void expectStatusNotNullNPE() {
+ expectedException.expect(NullPointerException.class);
+ expectedException.expectMessage("status can't be null");
+ }
+
+ private void expectCauseCannotBeEmptyIAE() {
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("cause can't be empty");
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/health/WebServerSafemodeCheckTest.java b/server/sonar-server/src/test/java/org/sonar/server/health/WebServerSafemodeCheckTest.java
new file mode 100644
index 00000000000..255ed328bc6
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/health/WebServerSafemodeCheckTest.java
@@ -0,0 +1,37 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class WebServerSafemodeCheckTest {
+ private WebServerSafemodeCheck underTest = new WebServerSafemodeCheck();
+
+ @Test
+ public void always_returns_RED_status_with_cause() {
+ Health health = underTest.check();
+
+ assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
+ assertThat(health.getCauses()).containsOnly("SonarQube webserver is not up");
+
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/health/WebServerStatusCheckTest.java b/server/sonar-server/src/test/java/org/sonar/server/health/WebServerStatusCheckTest.java
new file mode 100644
index 00000000000..8047c5ce3d9
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/health/WebServerStatusCheckTest.java
@@ -0,0 +1,96 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.health;
+
+import java.util.Arrays;
+import java.util.Random;
+import org.junit.Test;
+import org.sonar.server.app.RestartFlagHolder;
+import org.sonar.server.platform.Platform;
+import org.sonar.server.platform.db.migration.DatabaseMigrationState;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class WebServerStatusCheckTest {
+ private final DatabaseMigrationState migrationState = mock(DatabaseMigrationState.class);
+ private final Platform platform = mock(Platform.class);
+ private final RestartFlagHolder restartFlagHolder = mock(RestartFlagHolder.class);
+
+ private final Random random = new Random();
+
+ private WebServerStatusCheck underTest = new WebServerStatusCheck(migrationState, platform, restartFlagHolder);
+
+ @Test
+ public void returns_RED_status_with_cause_if_platform_status_is_not_UP() {
+ Platform.Status[] statusesButUp = Arrays.stream(Platform.Status.values())
+ .filter(s -> s != Platform.Status.UP)
+ .toArray(Platform.Status[]::new);
+ Platform.Status randomStatusButUp = statusesButUp[random.nextInt(statusesButUp.length)];
+ when(platform.status()).thenReturn(randomStatusButUp);
+
+ Health health = underTest.check();
+
+ verifyRedHealthWithCause(health);
+ }
+
+ @Test
+ public void returns_RED_status_with_cause_if_platform_status_is_UP_but_migrationStatus_is_neither_NONE_nor_SUCCEED() {
+ when(platform.status()).thenReturn(Platform.Status.UP);
+ DatabaseMigrationState.Status[] statusesButValidOnes = Arrays.stream(DatabaseMigrationState.Status.values())
+ .filter(s -> s != DatabaseMigrationState.Status.NONE)
+ .filter(s -> s != DatabaseMigrationState.Status.SUCCEEDED)
+ .toArray(DatabaseMigrationState.Status[]::new);
+ DatabaseMigrationState.Status randomInvalidStatus = statusesButValidOnes[random.nextInt(statusesButValidOnes.length)];
+ when(migrationState.getStatus()).thenReturn(randomInvalidStatus);
+
+ Health health = underTest.check();
+
+ verifyRedHealthWithCause(health);
+ }
+
+ @Test
+ public void returns_RED_with_cause_if_platform_status_is_UP_migration_status_is_valid_but_SQ_is_restarting() {
+ when(platform.status()).thenReturn(Platform.Status.UP);
+ when(migrationState.getStatus()).thenReturn(random.nextBoolean() ? DatabaseMigrationState.Status.NONE : DatabaseMigrationState.Status.SUCCEEDED);
+ when(restartFlagHolder.isRestarting()).thenReturn(true);
+
+ Health health = underTest.check();
+
+ verifyRedHealthWithCause(health);
+ }
+
+ @Test
+ public void returns_GREEN_without_cause_if_platform_status_is_UP_migration_status_is_valid_and_SQ_is_not_restarting() {
+ when(platform.status()).thenReturn(Platform.Status.UP);
+ when(migrationState.getStatus()).thenReturn(random.nextBoolean() ? DatabaseMigrationState.Status.NONE : DatabaseMigrationState.Status.SUCCEEDED);
+ when(restartFlagHolder.isRestarting()).thenReturn(false);
+
+ Health health = underTest.check();
+
+ assertThat(health).isEqualTo(Health.GREEN);
+ }
+
+ private void verifyRedHealthWithCause(Health health) {
+ assertThat(health.getStatus()).isEqualTo(Health.Status.RED);
+ assertThat(health.getCauses()).containsOnly("SonarQube webserver is not up");
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/AbstractHealthActionTestSupport.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/AbstractHealthActionTestSupport.java
new file mode 100644
index 00000000000..91985e5f063
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/AbstractHealthActionTestSupport.java
@@ -0,0 +1,76 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.platform.ws;
+
+import java.util.Random;
+import java.util.stream.IntStream;
+import org.apache.commons.lang.RandomStringUtils;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.health.Health;
+import org.sonar.server.health.HealthChecker;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+import org.sonar.test.JsonAssert;
+import org.sonarqube.ws.WsSystem;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.health.Health.newHealthCheckBuilder;
+
+public class AbstractHealthActionTestSupport {
+ HealthChecker mockedHealthChecker = mock(HealthChecker.class);
+
+ void verifyDefinition(WebService.Action definition) {
+ assertThat(definition.key()).isEqualTo("health");
+ assertThat(definition.isPost()).isFalse();
+ assertThat(definition.description()).isNotEmpty();
+ assertThat(definition.since()).isEqualTo("6.6");
+ assertThat(definition.isInternal()).isFalse();
+ assertThat(definition.responseExample()).isNotNull();
+ assertThat(definition.params()).isEmpty();
+ }
+
+ void verifyExample(WsActionTester underTest) {
+ when(mockedHealthChecker.check()).thenReturn(
+ newHealthCheckBuilder()
+ .setStatus(Health.Status.YELLOW)
+ .addCause("Elasticsearch status is YELLOW")
+ .build());
+ TestRequest request = underTest.newRequest();
+
+ JsonAssert.assertJson(request.execute().getInput())
+ .isSimilarTo(underTest.getDef().responseExampleAsString());
+ }
+
+ void requestReturnsStatusAndCausesFromHealthCheckerCheckMethod(WsActionTester underTest) {
+ Health.Status randomStatus = Health.Status.values()[new Random().nextInt(Health.Status.values().length)];
+ Health.Builder builder = newHealthCheckBuilder()
+ .setStatus(randomStatus);
+ IntStream.range(0, new Random().nextInt(5)).mapToObj(i -> RandomStringUtils.randomAlphanumeric(3)).forEach(builder::addCause);
+ Health health = builder.build();
+ when(mockedHealthChecker.check()).thenReturn(health);
+ TestRequest request = underTest.newRequest();
+
+ WsSystem.HealthResponse healthResponse = request.executeProtobuf(WsSystem.HealthResponse.class);
+ assertThat(healthResponse.getHealth().name()).isEqualTo(randomStatus.name());
+ assertThat(health.getCauses()).isEqualTo(health.getCauses());
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/HealthActionModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/HealthActionModuleTest.java
new file mode 100644
index 00000000000..32d54bf4b1c
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/HealthActionModuleTest.java
@@ -0,0 +1,69 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.platform.ws;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.picocontainer.ComponentAdapter;
+import org.sonar.core.platform.ComponentContainer;
+import org.sonar.server.health.DbConnectionCheck;
+import org.sonar.server.health.EsStatusCheck;
+import org.sonar.server.health.HealthCheck;
+import org.sonar.server.health.HealthCheckerImpl;
+import org.sonar.server.health.WebServerStatusCheck;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class HealthActionModuleTest {
+ private HealthActionModule underTest = new HealthActionModule();
+
+ @Test
+ public void verify_action_and_HealthChecker() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.configure(container);
+
+ assertThat(classesAddedToContainer(container))
+ .contains(HealthCheckerImpl.class)
+ .contains(HealthAction.class)
+ .doesNotContain(SafeModeHealthAction.class);
+ }
+
+ @Test
+ public void verify_installed_HealthChecks_implementations() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.configure(container);
+
+ List<Class<?>> checks = classesAddedToContainer(container).stream().filter(HealthCheck.class::isAssignableFrom).collect(Collectors.toList());
+ assertThat(checks)
+ .hasSize(3)
+ .contains(WebServerStatusCheck.class)
+ .contains(DbConnectionCheck.class)
+ .contains(EsStatusCheck.class);
+ }
+
+ private List<Class<?>> classesAddedToContainer(ComponentContainer container) {
+ Collection<ComponentAdapter<?>> componentAdapters = container.getPicoContainer().getComponentAdapters();
+ return componentAdapters.stream().map(ComponentAdapter::getComponentImplementation).collect(Collectors.toList());
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/HealthActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/HealthActionTest.java
new file mode 100644
index 00000000000..201b34fd328
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/HealthActionTest.java
@@ -0,0 +1,93 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.platform.ws;
+
+import java.util.Random;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.exceptions.ForbiddenException;
+import org.sonar.server.exceptions.UnauthorizedException;
+import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
+import org.sonar.server.ws.WsActionTester;
+
+public class HealthActionTest {
+ @Rule
+ public UserSessionRule userSessionRule = UserSessionRule.standalone();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private AbstractHealthActionTestSupport healthActionTestSupport = new AbstractHealthActionTestSupport();
+ private WsActionTester underTest = new WsActionTester(new HealthAction(userSessionRule, healthActionTestSupport.mockedHealthChecker));
+
+ @Test
+ public void verify_definition() {
+ WebService.Action definition = underTest.getDef();
+
+ healthActionTestSupport.verifyDefinition(definition);
+ }
+
+ @Test
+ public void execute_fails_with_UnauthorizedException_if_user_is_not_logged_in() {
+ TestRequest request = underTest.newRequest();
+
+ expectedException.expect(UnauthorizedException.class);
+ expectedException.expectMessage("Authentication is required");
+
+ request.execute();
+ }
+
+ @Test
+ public void execute_fails_with_ForbiddenException_if_user_logged_in_but_not_root() {
+ TestRequest request = underTest.newRequest();
+ userSessionRule.logIn();
+
+ expectedException.expect(ForbiddenException.class);
+ expectedException.expectMessage("Insufficient privileges");
+
+ request.execute();
+ }
+
+ @Test
+ public void verify_example() {
+ userSessionRule.logIn();
+ rootOrSystemAdmin();
+
+ healthActionTestSupport.verifyExample(underTest);
+ }
+
+ @Test
+ public void request_returns_status_and_causes_from_HealthChecker_check_method() {
+ userSessionRule.logIn();
+ rootOrSystemAdmin();
+
+ healthActionTestSupport.requestReturnsStatusAndCausesFromHealthCheckerCheckMethod(underTest);
+ }
+
+ private void rootOrSystemAdmin() {
+ if (new Random().nextBoolean()) {
+ userSessionRule.setRoot();
+ } else {
+ userSessionRule.setSystemAdministrator();
+ }
+ }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SafeModeHealthActionModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SafeModeHealthActionModuleTest.java
new file mode 100644
index 00000000000..f99fa201a33
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SafeModeHealthActionModuleTest.java
@@ -0,0 +1,70 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.platform.ws;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.picocontainer.ComponentAdapter;
+import org.sonar.core.platform.ComponentContainer;
+import org.sonar.server.health.DbConnectionCheck;
+import org.sonar.server.health.EsStatusCheck;
+import org.sonar.server.health.HealthCheck;
+import org.sonar.server.health.HealthCheckerImpl;
+import org.sonar.server.health.WebServerSafemodeCheck;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class SafeModeHealthActionModuleTest {
+ private SafeModeHealthActionModule underTest = new SafeModeHealthActionModule();
+
+ @Test
+ public void verify_action_and_HealthChecker() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.configure(container);
+
+ assertThat(classesAddedToContainer(container))
+ .contains(HealthCheckerImpl.class)
+ .contains(SafeModeHealthAction.class)
+ .doesNotContain(HealthAction.class);
+ }
+
+ @Test
+ public void verify_installed_HealthChecks_implementations() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.configure(container);
+
+ List<Class<?>> checks = classesAddedToContainer(container).stream().filter(HealthCheck.class::isAssignableFrom).collect(Collectors.toList());
+ assertThat(checks)
+ .hasSize(3)
+ .contains(WebServerSafemodeCheck.class)
+ .contains(DbConnectionCheck.class)
+ .contains(EsStatusCheck.class);
+ }
+
+ private List<Class<?>> classesAddedToContainer(ComponentContainer container) {
+ Collection<ComponentAdapter<?>> componentAdapters = container.getPicoContainer().getComponentAdapters();
+ return componentAdapters.stream().map(ComponentAdapter::getComponentImplementation).collect(Collectors.toList());
+ }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SafeModeHealthActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SafeModeHealthActionTest.java
new file mode 100644
index 00000000000..0cec5200305
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/ws/SafeModeHealthActionTest.java
@@ -0,0 +1,47 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.platform.ws;
+
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.server.ws.WsActionTester;
+
+public class SafeModeHealthActionTest {
+ private AbstractHealthActionTestSupport healthActionTestSupport = new AbstractHealthActionTestSupport();
+ private WsActionTester underTest = new WsActionTester(new SafeModeHealthAction(healthActionTestSupport.mockedHealthChecker));
+
+ @Test
+ public void verify_definition() {
+ WebService.Action definition = underTest.getDef();
+
+ healthActionTestSupport.verifyDefinition(definition);
+ }
+
+ @Test
+ public void verify_example() {
+ healthActionTestSupport.verifyExample(underTest);
+ }
+
+ @Test
+ public void request_returns_status_and_causes_from_HealthChecker_check_method() {
+ healthActionTestSupport.requestReturnsStatusAndCausesFromHealthCheckerCheckMethod(underTest);
+ }
+
+}
diff --git a/sonar-ws/src/main/protobuf/ws-system.proto b/sonar-ws/src/main/protobuf/ws-system.proto
new file mode 100644
index 00000000000..4f0fd9af5fc
--- /dev/null
+++ b/sonar-ws/src/main/protobuf/ws-system.proto
@@ -0,0 +1,41 @@
+// SonarQube, open source software quality management tool.
+// Copyright (C) 2008-2016 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.
+
+syntax = "proto2";
+
+package sonarqube.ws.system;
+
+option java_package = "org.sonarqube.ws";
+option java_outer_classname = "WsSystem";
+option optimize_for = SPEED;
+
+// GET api/system/health
+message HealthResponse {
+ optional Health health = 1;
+ repeated Cause causes = 2;
+}
+
+message Cause {
+ optional string message = 1;
+}
+
+enum Health {
+ GREEN = 0;
+ YELLOW = 1;
+ RED = 2;
+}