aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2017-09-04 11:35:20 +0200
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2017-09-13 15:50:51 +0200
commit55de8d527c66d2a7d87014f89415346722c78644 (patch)
tree85ab1e4c64903ea0d58e714bbbb812f52c01a680 /server
parent17dc4c8315d78a346df6e2b93313d6bc72f14992 (diff)
downloadsonarqube-55de8d527c66d2a7d87014f89415346722c78644.tar.gz
sonarqube-55de8d527c66d2a7d87014f89415346722c78644.zip
SONAR-9741 add HealthChecker#checkCluster with check of ES status
Diffstat (limited to 'server')
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/ClusterHealthCheck.java27
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/EsStatusCheck.java55
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/EsStatusClusterCheck.java37
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/EsStatusNodeCheck.java29
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/HealthChecker.java7
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/HealthCheckerImpl.java49
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthActionModule.java4
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/health/EsStatusClusterCheckTest.java63
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/health/HealthCheckerImplTest.java233
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/platform/ws/HealthActionModuleTest.java14
10 files changed, 456 insertions, 62 deletions
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/ClusterHealthCheck.java b/server/sonar-server/src/main/java/org/sonar/server/health/ClusterHealthCheck.java
new file mode 100644
index 00000000000..ddb2644d370
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/ClusterHealthCheck.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;
+
+import java.util.Set;
+import org.sonar.cluster.health.NodeHealth;
+
+public interface ClusterHealthCheck {
+ Health check(Set<NodeHealth> nodeHealths);
+}
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..1110a9e362e
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/EsStatusCheck.java
@@ -0,0 +1,55 @@
+/*
+ * 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;
+
+public abstract class EsStatusCheck {
+ 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();
+ protected final EsClient esClient;
+
+ EsStatusCheck(EsClient esClient) {
+ this.esClient = esClient;
+ }
+
+ Health checkEsStatus() {
+ 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/EsStatusClusterCheck.java b/server/sonar-server/src/main/java/org/sonar/server/health/EsStatusClusterCheck.java
new file mode 100644
index 00000000000..0c21eecb282
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/EsStatusClusterCheck.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 java.util.Set;
+import org.sonar.cluster.health.NodeHealth;
+import org.sonar.server.es.EsClient;
+
+public class EsStatusClusterCheck extends EsStatusCheck implements ClusterHealthCheck {
+
+ public EsStatusClusterCheck(EsClient esClient) {
+ super(esClient);
+ }
+
+ @Override
+ public Health check(Set<NodeHealth> nodeHealths) {
+ return checkEsStatus();
+ }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/EsStatusNodeCheck.java b/server/sonar-server/src/main/java/org/sonar/server/health/EsStatusNodeCheck.java
index 34d190de136..8c4cd5f5357 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/health/EsStatusNodeCheck.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/EsStatusNodeCheck.java
@@ -19,42 +19,19 @@
*/
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 EsStatusNodeCheck implements NodeHealthCheck {
- 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 class EsStatusNodeCheck extends EsStatusCheck implements NodeHealthCheck {
public EsStatusNodeCheck(EsClient esClient) {
- this.esClient = esClient;
+ super(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);
- }
+ return super.checkEsStatus();
}
}
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
index 2dccef66743..f455e97d52b 100644
--- 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
@@ -25,4 +25,11 @@ public interface HealthChecker {
* of a cluster.
*/
Health checkNode();
+
+ /**
+ * Perform a check of the health of the SonarQube cluster.
+ *
+ * @throws IllegalStateException if clustering is not enabled.
+ */
+ Health checkCluster();
}
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
index 0904003062e..630e308c06f 100644
--- 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
@@ -19,27 +19,64 @@
*/
package org.sonar.server.health;
-import java.util.Arrays;
import java.util.List;
+import java.util.Set;
import java.util.function.BinaryOperator;
import java.util.stream.Stream;
+import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.cluster.health.NodeHealth;
+import org.sonar.cluster.health.SharedHealthState;
+import org.sonar.server.platform.WebServer;
+import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.collect.ImmutableList.copyOf;
import static org.sonar.server.health.Health.newHealthCheckBuilder;
/**
- * Implementation of {@link HealthChecker} that executes implementations of {@link NodeHealthCheck} in the container
- * and aggregates their results.
+ * Implementation of {@link HealthChecker} based on {@link NodeHealthCheck} and {@link ClusterHealthCheck} instances
+ * available in the container.
*/
public class HealthCheckerImpl implements HealthChecker {
+ private final WebServer webServer;
private final List<NodeHealthCheck> nodeHealthChecks;
+ private final List<ClusterHealthCheck> clusterHealthChecks;
+ @CheckForNull
+ private final SharedHealthState sharedHealthState;
- public HealthCheckerImpl(NodeHealthCheck... nodeHealthChecks) {
- this.nodeHealthChecks = Arrays.asList(nodeHealthChecks);
+ /**
+ * Constructor used by Pico in standalone mode and in safe mode.
+ */
+ public HealthCheckerImpl(WebServer webServer, NodeHealthCheck[] nodeHealthChecks) {
+ this(webServer, nodeHealthChecks, new ClusterHealthCheck[0], null);
+ }
+
+ /**
+ * Constructor used by Pico in cluster mode.
+ */
+ public HealthCheckerImpl(WebServer webServer, NodeHealthCheck[] nodeHealthChecks, ClusterHealthCheck[] clusterHealthChecks,
+ @Nullable SharedHealthState sharedHealthState) {
+ this.webServer = webServer;
+ this.nodeHealthChecks = copyOf(nodeHealthChecks);
+ this.clusterHealthChecks = copyOf(clusterHealthChecks);
+ this.sharedHealthState = sharedHealthState;
}
@Override
public Health checkNode() {
- return nodeHealthChecks.stream().map(NodeHealthCheck::check)
+ return nodeHealthChecks.stream()
+ .map(NodeHealthCheck::check)
+ .reduce(Health.GREEN, HealthReducer.INSTANCE);
+ }
+
+ @Override
+ public Health checkCluster() {
+ checkState(!webServer.isStandalone(), "Clustering is not enabled");
+ checkState(sharedHealthState != null, "HealthState instance can't be null when clustering is enabled");
+
+ Set<NodeHealth> nodeHealths = sharedHealthState.readAll();
+ return clusterHealthChecks.stream()
+ .map(clusterHealthCheck -> clusterHealthCheck.check(nodeHealths))
.reduce(Health.GREEN, HealthReducer.INSTANCE);
}
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
index 871a9314ee6..fe7f38efd12 100644
--- 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
@@ -22,6 +22,7 @@ package org.sonar.server.platform.ws;
import org.sonar.core.platform.Module;
import org.sonar.server.health.CeStatusNodeCheck;
import org.sonar.server.health.DbConnectionNodeCheck;
+import org.sonar.server.health.EsStatusClusterCheck;
import org.sonar.server.health.EsStatusNodeCheck;
import org.sonar.server.health.HealthCheckerImpl;
import org.sonar.server.health.WebServerStatusNodeCheck;
@@ -36,6 +37,9 @@ public class HealthActionModule extends Module {
EsStatusNodeCheck.class,
CeStatusNodeCheck.class);
+ // ClusterHealthCheck implementations
+ add(EsStatusClusterCheck.class);
+
add(HealthCheckerImpl.class,
HealthAction.class);
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/health/EsStatusClusterCheckTest.java b/server/sonar-server/src/test/java/org/sonar/server/health/EsStatusClusterCheckTest.java
new file mode 100644
index 00000000000..e91143f5016
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/health/EsStatusClusterCheckTest.java
@@ -0,0 +1,63 @@
+/*
+ * 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.Random;
+import java.util.Set;
+import java.util.stream.Collectors;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.cluster.health.NodeDetails;
+import org.sonar.cluster.health.NodeHealth;
+import org.sonar.server.es.EsTester;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class EsStatusClusterCheckTest {
+
+ @Rule
+ public EsTester esTester = new EsTester();
+
+ private EsStatusClusterCheck underTest = new EsStatusClusterCheck(esTester.client());
+
+ @Test
+ public void check_ignores_NodeHealth_arg_and_returns_GREEN_without_cause_if_ES_cluster_status_is_GREEN() {
+ Random random = new Random();
+ Set<NodeHealth> nodeHealths = IntStream.range(0, random.nextInt(20))
+ .mapToObj(i -> NodeHealth.newNodeHealthBuilder()
+ .setStatus(NodeHealth.Status.values()[random.nextInt(NodeHealth.Status.values().length)])
+ .setDetails(NodeDetails.newNodeDetailsBuilder()
+ .setType(random.nextBoolean() ? NodeDetails.Type.APPLICATION : NodeDetails.Type.SEARCH)
+ .setName(randomAlphanumeric(23))
+ .setHost(randomAlphanumeric(23))
+ .setPort(1 + random.nextInt(96))
+ .setStarted(1 + random.nextInt(966))
+ .build())
+ .setDate(1 + random.nextInt(23))
+ .build())
+ .collect(Collectors.toSet());
+ Health health = underTest.check(nodeHealths);
+
+ 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
index 42c1b93673d..9f4ec3ebe7a 100644
--- 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
@@ -24,96 +24,250 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Random;
+import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
-import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.cluster.health.NodeDetails;
+import org.sonar.cluster.health.NodeHealth;
+import org.sonar.cluster.health.SharedHealthState;
+import org.sonar.server.platform.WebServer;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.same;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+import static org.sonar.cluster.health.NodeDetails.newNodeDetailsBuilder;
+import static org.sonar.cluster.health.NodeHealth.newNodeHealthBuilder;
+import static org.sonar.server.health.Health.newHealthCheckBuilder;
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 {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ private final WebServer webServer = mock(WebServer.class);
+ private final SharedHealthState sharedHealthState = mock(SharedHealthState.class);
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();
+ public void check_returns_green_status_without_any_cause_when_there_is_no_NodeHealthCheck() {
+ HealthCheckerImpl underTest = new HealthCheckerImpl(webServer, new NodeHealthCheck[0]);
assertThat(underTest.checkNode()).isEqualTo(Health.GREEN);
}
@Test
- public void checkNode_returns_GREEN_status_if_only_GREEN_statuses_returned_by_HealthChecks() {
+ public void checkNode_returns_GREEN_status_if_only_GREEN_statuses_returned_by_NodeHealthCheck() {
List<Health.Status> statuses = IntStream.range(1, 1 + random.nextInt(20)).mapToObj(i -> GREEN).collect(Collectors.toList());
- HealthCheckerImpl underTest = newHealthCheckerImpl(statuses.stream());
+ HealthCheckerImpl underTest = newNodeHealthCheckerImpl(statuses.stream());
assertThat(underTest.checkNode().getStatus())
- .describedAs("%s should have been computed from %s statuses", GREEN, statuses)
- .isEqualTo(GREEN);
+ .describedAs("%s should have been computed from %s statuses", GREEN, statuses)
+ .isEqualTo(GREEN);
}
@Test
- public void checkNode_returns_YELLOW_status_if_only_GREEN_and_at_least_one_YELLOW_statuses_returned_by_HealthChecks() {
+ public void checkNode_returns_YELLOW_status_if_only_GREEN_and_at_least_one_YELLOW_statuses_returned_by_NodeHealthCheck() {
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
+ 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());
+ HealthCheckerImpl underTest = newNodeHealthCheckerImpl(statuses.stream());
assertThat(underTest.checkNode().getStatus())
- .describedAs("%s should have been computed from %s statuses", YELLOW, statuses)
- .isEqualTo(YELLOW);
+ .describedAs("%s should have been computed from %s statuses", YELLOW, statuses)
+ .isEqualTo(YELLOW);
}
@Test
- public void checkNode_returns_RED_status_if_at_least_one_RED_status_returned_by_HealthChecks() {
+ public void checkNode_returns_RED_status_if_at_least_one_RED_status_returned_by_NodeHealthCheck() {
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
+ 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);
+ .forEach(statuses::add);
Collections.shuffle(statuses);
- HealthCheckerImpl underTest = newHealthCheckerImpl(statuses.stream());
+ HealthCheckerImpl underTest = newNodeHealthCheckerImpl(statuses.stream());
assertThat(underTest.checkNode().getStatus())
- .describedAs("%s should have been computed from %s statuses", RED, statuses)
- .isEqualTo(RED);
+ .describedAs("%s should have been computed from %s statuses", RED, statuses)
+ .isEqualTo(RED);
}
@Test
- public void checkNode_returns_causes_of_all_HealthChecks_whichever_their_status() {
+ public void checkNode_returns_causes_of_all_NodeHealthCheck_whichever_their_status() {
NodeHealthCheck[] nodeHealthChecks = IntStream.range(0, 1 + random.nextInt(20))
- .mapToObj(s -> new HardcodedHealthNodeCheck(IntStream.range(0, random.nextInt(3)).mapToObj(i -> RandomStringUtils.randomAlphanumeric(3)).toArray(String[]::new)))
- .map(NodeHealthCheck.class::cast)
- .toArray(NodeHealthCheck[]::new);
+ .mapToObj(s -> new HardcodedHealthNodeCheck(IntStream.range(0, random.nextInt(3)).mapToObj(i -> randomAlphanumeric(3)).toArray(String[]::new)))
+ .map(NodeHealthCheck.class::cast)
+ .toArray(NodeHealthCheck[]::new);
String[] expected = Arrays.stream(nodeHealthChecks).map(NodeHealthCheck::check).flatMap(s -> s.getCauses().stream()).toArray(String[]::new);
- HealthCheckerImpl underTest = new HealthCheckerImpl(nodeHealthChecks);
+ HealthCheckerImpl underTest = new HealthCheckerImpl(webServer, nodeHealthChecks);
assertThat(underTest.checkNode().getCauses()).containsOnly(expected);
}
- private HealthCheckerImpl newHealthCheckerImpl(Stream<Health.Status> statuses) {
+ @Test
+ public void checkCluster_fails_with_ISE_in_standalone() {
+ when(webServer.isStandalone()).thenReturn(true);
+ HealthCheckerImpl underTest = new HealthCheckerImpl(webServer, new NodeHealthCheck[0], new ClusterHealthCheck[0], sharedHealthState);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Clustering is not enabled");
+
+ underTest.checkCluster();
+ }
+
+ @Test
+ public void checkCluster_fails_with_ISE_in_clustering_and_HealthState_is_null() {
+ when(webServer.isStandalone()).thenReturn(false);
+ HealthCheckerImpl underTest = new HealthCheckerImpl(webServer, new NodeHealthCheck[0], new ClusterHealthCheck[0], sharedHealthState);
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("HealthState instance can't be null when clustering is enabled");
+
+ underTest.checkCluster();
+ }
+
+ @Test
+ public void checkCluster_returns_GREEN_when_there_is_no_ClusterHealthCheck() {
+ when(webServer.isStandalone()).thenReturn(false);
+ HealthCheckerImpl underTest = new HealthCheckerImpl(webServer, new NodeHealthCheck[0], new ClusterHealthCheck[0], sharedHealthState);
+
+ assertThat(underTest.checkCluster()).isEqualTo(Health.GREEN);
+ }
+
+ @Test
+ public void checkCluster_returns_GREEN_status_if_only_GREEN_statuses_returned_by_ClusterHealthChecks() {
+ when(webServer.isStandalone()).thenReturn(false);
+ List<Health.Status> statuses = IntStream.range(1, 1 + random.nextInt(20)).mapToObj(i -> GREEN).collect(Collectors.toList());
+ HealthCheckerImpl underTest = newClusterHealthCheckerImpl(statuses.stream());
+
+ assertThat(underTest.checkCluster().getStatus())
+ .describedAs("%s should have been computed from %s statuses", GREEN, statuses)
+ .isEqualTo(GREEN);
+ }
+
+ @Test
+ public void checkCluster_returns_YELLOW_status_if_only_GREEN_and_at_least_one_YELLOW_statuses_returned_by_ClusterHealthChecks() {
+ when(webServer.isStandalone()).thenReturn(false);
+ 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 = newClusterHealthCheckerImpl(statuses.stream());
+
+ assertThat(underTest.checkCluster().getStatus())
+ .describedAs("%s should have been computed from %s statuses", YELLOW, statuses)
+ .isEqualTo(YELLOW);
+ }
+
+ @Test
+ public void checkCluster_returns_RED_status_if_at_least_one_RED_status_returned_by_ClusterHealthChecks() {
+ when(webServer.isStandalone()).thenReturn(false);
+ 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 = newClusterHealthCheckerImpl(statuses.stream());
+
+ assertThat(underTest.checkCluster().getStatus())
+ .describedAs("%s should have been computed from %s statuses", RED, statuses)
+ .isEqualTo(RED);
+ }
+
+ @Test
+ public void checkCluster_returns_causes_of_all_ClusterHealthChecks_whichever_their_status() {
+ when(webServer.isStandalone()).thenReturn(false);
+ List<String[]> causesGroups = IntStream.range(0, 1 + random.nextInt(20))
+ .mapToObj(s -> IntStream.range(0, random.nextInt(3)).mapToObj(i -> randomAlphanumeric(3)).toArray(String[]::new))
+ .collect(Collectors.toList());
+ ClusterHealthCheck[] clusterHealthChecks = causesGroups.stream()
+ .map(HardcodedHealthClusterCheck::new)
+ .map(ClusterHealthCheck.class::cast)
+ .toArray(ClusterHealthCheck[]::new);
+ String[] expectedCauses = causesGroups.stream().flatMap(Arrays::stream).collect(Collectors.toSet()).stream().toArray(String[]::new);
+
+ HealthCheckerImpl underTest = new HealthCheckerImpl(webServer, new NodeHealthCheck[0], clusterHealthChecks, sharedHealthState);
+
+ assertThat(underTest.checkCluster().getCauses()).containsOnly(expectedCauses);
+ }
+
+ @Test
+ public void checkCluster_passes_set_of_NodeHealth_returns_by_HealthState_to_all_ClusterHealthChecks() {
+ when(webServer.isStandalone()).thenReturn(false);
+ ClusterHealthCheck[] mockedClusterHealthChecks = IntStream.range(0, 1 + random.nextInt(3))
+ .mapToObj(i -> mock(ClusterHealthCheck.class))
+ .toArray(ClusterHealthCheck[]::new);
+ Set<NodeHealth> nodeHealths = IntStream.range(0, 1 + random.nextInt(4)).mapToObj(i -> randomNodeHealth()).collect(Collectors.toSet());
+ when(sharedHealthState.readAll()).thenReturn(nodeHealths);
+ for (ClusterHealthCheck mockedClusterHealthCheck : mockedClusterHealthChecks) {
+ when(mockedClusterHealthCheck.check(same(nodeHealths))).thenReturn(Health.GREEN);
+ }
+
+ HealthCheckerImpl underTest = new HealthCheckerImpl(webServer, new NodeHealthCheck[0], mockedClusterHealthChecks, sharedHealthState);
+ underTest.checkCluster();
+
+ for (ClusterHealthCheck mockedClusterHealthCheck : mockedClusterHealthChecks) {
+ verify(mockedClusterHealthCheck).check(same(nodeHealths));
+ }
+ }
+
+ private NodeHealth randomNodeHealth() {
+ return newNodeHealthBuilder()
+ .setStatus(NodeHealth.Status.values()[random.nextInt(NodeHealth.Status.values().length)])
+ .setDate(1 + random.nextInt(222))
+ .setDetails(newNodeDetailsBuilder()
+ .setType(random.nextBoolean() ? NodeDetails.Type.APPLICATION : NodeDetails.Type.SEARCH)
+ .setName(randomAlphanumeric(10))
+ .setHost(randomAlphanumeric(5))
+ .setPort(1 + random.nextInt(333))
+ .setStarted(1 + random.nextInt(444))
+ .build())
+ .build();
+ }
+
+ private HealthCheckerImpl newNodeHealthCheckerImpl(Stream<Health.Status> statuses) {
Stream<HardcodedHealthNodeCheck> staticHealthCheckStream = statuses.map(HardcodedHealthNodeCheck::new);
- return new HealthCheckerImpl(staticHealthCheckStream.map(NodeHealthCheck.class::cast).toArray(NodeHealthCheck[]::new));
+ return new HealthCheckerImpl(
+ webServer,
+ staticHealthCheckStream.map(NodeHealthCheck.class::cast).toArray(NodeHealthCheck[]::new));
+ }
+
+ private HealthCheckerImpl newClusterHealthCheckerImpl(Stream<Health.Status> statuses) {
+ Stream<HardcodedHealthClusterCheck> staticHealthCheckStream = statuses.map(HardcodedHealthClusterCheck::new);
+ return new HealthCheckerImpl(
+ webServer,
+ new NodeHealthCheck[0],
+ staticHealthCheckStream.map(ClusterHealthCheck.class::cast).toArray(ClusterHealthCheck[]::new),
+ sharedHealthState);
}
private class HardcodedHealthNodeCheck implements NodeHealthCheck {
private final Health health;
public HardcodedHealthNodeCheck(Health.Status status) {
- this.health = Health.newHealthCheckBuilder().setStatus(status).build();
+ this.health = newHealthCheckBuilder().setStatus(status).build();
}
public HardcodedHealthNodeCheck(String... causes) {
- Health.Builder builder = Health.newHealthCheckBuilder().setStatus(Health.Status.values()[random.nextInt(3)]);
+ Health.Builder builder = newHealthCheckBuilder().setStatus(Health.Status.values()[random.nextInt(3)]);
Stream.of(causes).forEach(builder::addCause);
this.health = builder.build();
}
@@ -123,4 +277,23 @@ public class HealthCheckerImplTest {
return health;
}
}
+
+ private class HardcodedHealthClusterCheck implements ClusterHealthCheck {
+ private final Health health;
+
+ public HardcodedHealthClusterCheck(Health.Status status) {
+ this.health = newHealthCheckBuilder().setStatus(status).build();
+ }
+
+ public HardcodedHealthClusterCheck(String... causes) {
+ Health.Builder builder = newHealthCheckBuilder().setStatus(Health.Status.values()[random.nextInt(3)]);
+ Stream.of(causes).forEach(builder::addCause);
+ this.health = builder.build();
+ }
+
+ @Override
+ public Health check(Set<NodeHealth> nodeHealths) {
+ return health;
+ }
+ }
}
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
index f4bc59280d4..2ee0421796b 100644
--- 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
@@ -26,7 +26,9 @@ import org.junit.Test;
import org.picocontainer.ComponentAdapter;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.server.health.CeStatusNodeCheck;
+import org.sonar.server.health.ClusterHealthCheck;
import org.sonar.server.health.DbConnectionNodeCheck;
+import org.sonar.server.health.EsStatusClusterCheck;
import org.sonar.server.health.EsStatusNodeCheck;
import org.sonar.server.health.HealthCheckerImpl;
import org.sonar.server.health.NodeHealthCheck;
@@ -63,6 +65,18 @@ public class HealthActionModuleTest {
.contains(CeStatusNodeCheck.class);
}
+ @Test
+ public void verify_installed_ClusterHealthChecks_implementations() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.configure(container);
+
+ List<Class<?>> checks = classesAddedToContainer(container).stream().filter(ClusterHealthCheck.class::isAssignableFrom).collect(Collectors.toList());
+ assertThat(checks)
+ .hasSize(1)
+ .contains(EsStatusClusterCheck.class);
+ }
+
private List<Class<?>> classesAddedToContainer(ComponentContainer container) {
Collection<ComponentAdapter<?>> componentAdapters = container.getPicoContainer().getComponentAdapters();
return componentAdapters.stream().map(ComponentAdapter::getComponentImplementation).collect(Collectors.toList());