aboutsummaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>2017-08-30 10:53:17 +0200
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>2017-09-13 15:50:51 +0200
commit17dc4c8315d78a346df6e2b93313d6bc72f14992 (patch)
tree503b96d296e2859ccd1f7a24aac8374c6340c0c8 /server
parent0d06d27d3c0f1a35384b40926e34dc0020de998d (diff)
downloadsonarqube-17dc4c8315d78a346df6e2b93313d6bc72f14992.tar.gz
sonarqube-17dc4c8315d78a346df6e2b93313d6bc72f14992.zip
SONAR-9741 web process shares health info in Hazelcast
Diffstat (limited to 'server')
-rw-r--r--server/sonar-cluster/src/main/java/org/sonar/cluster/health/HealthStateRefresher.java61
-rw-r--r--server/sonar-cluster/src/main/java/org/sonar/cluster/health/HealthStateRefresherExecutorService.java25
-rw-r--r--server/sonar-cluster/src/test/java/org/sonar/cluster/health/HealthStateRefresherTest.java97
-rw-r--r--server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeDetailsTest.java35
-rw-r--r--server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeDetailsTestSupport.java89
-rw-r--r--server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeHealthTest.java66
-rw-r--r--server/sonar-server/pom.xml33
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/HealthStateRefresherExecutorServiceImpl.java38
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/NodeHealthModule.java35
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/health/NodeHealthProviderImpl.java88
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java4
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/HealthActionModule.java11
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/health/NodeHealthModuleTest.java87
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/health/NodeHealthProviderImplTest.java242
15 files changed, 818 insertions, 99 deletions
diff --git a/server/sonar-cluster/src/main/java/org/sonar/cluster/health/HealthStateRefresher.java b/server/sonar-cluster/src/main/java/org/sonar/cluster/health/HealthStateRefresher.java
new file mode 100644
index 00000000000..d37989dfa2d
--- /dev/null
+++ b/server/sonar-cluster/src/main/java/org/sonar/cluster/health/HealthStateRefresher.java
@@ -0,0 +1,61 @@
+/*
+ * 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.cluster.health;
+
+import java.util.concurrent.TimeUnit;
+import org.picocontainer.Startable;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+
+public class HealthStateRefresher implements Startable {
+ private static final Logger LOG = Loggers.get(HealthStateRefresher.class);
+ private static final int INITIAL_DELAY = 1;
+ private static final int DELAY = 10;
+
+ private final HealthStateRefresherExecutorService executorService;
+ private final NodeHealthProvider nodeHealthProvider;
+ private final SharedHealthState sharedHealthState;
+
+ public HealthStateRefresher(HealthStateRefresherExecutorService executorService, NodeHealthProvider nodeHealthProvider,
+ SharedHealthState sharedHealthState) {
+ this.executorService = executorService;
+ this.nodeHealthProvider = nodeHealthProvider;
+ this.sharedHealthState = sharedHealthState;
+ }
+
+ @Override
+ public void start() {
+ executorService.scheduleWithFixedDelay(this::refresh, INITIAL_DELAY, DELAY, TimeUnit.SECONDS);
+ }
+
+ private void refresh() {
+ try {
+ NodeHealth nodeHealth = nodeHealthProvider.get();
+ sharedHealthState.writeMine(nodeHealth);
+ } catch (Throwable t) {
+ LOG.error("An error occurred while attempting to refresh HealthState of the current node in the shared state:", t);
+ }
+ }
+
+ @Override
+ public void stop() {
+ // nothing to do
+ }
+}
diff --git a/server/sonar-cluster/src/main/java/org/sonar/cluster/health/HealthStateRefresherExecutorService.java b/server/sonar-cluster/src/main/java/org/sonar/cluster/health/HealthStateRefresherExecutorService.java
new file mode 100644
index 00000000000..5fd841ff591
--- /dev/null
+++ b/server/sonar-cluster/src/main/java/org/sonar/cluster/health/HealthStateRefresherExecutorService.java
@@ -0,0 +1,25 @@
+/*
+ * 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.cluster.health;
+
+import java.util.concurrent.ScheduledExecutorService;
+
+public interface HealthStateRefresherExecutorService extends ScheduledExecutorService {
+}
diff --git a/server/sonar-cluster/src/test/java/org/sonar/cluster/health/HealthStateRefresherTest.java b/server/sonar-cluster/src/test/java/org/sonar/cluster/health/HealthStateRefresherTest.java
new file mode 100644
index 00000000000..edcc89af1fa
--- /dev/null
+++ b/server/sonar-cluster/src/test/java/org/sonar/cluster/health/HealthStateRefresherTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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.cluster.health;
+
+import java.util.Random;
+import java.util.concurrent.TimeUnit;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.mockito.ArgumentCaptor;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.fail;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class HealthStateRefresherTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ private Random random = new Random();
+ private NodeDetailsTestSupport testSupport = new NodeDetailsTestSupport(random);
+
+ private HealthStateRefresherExecutorService executorService = mock(HealthStateRefresherExecutorService.class);
+ private NodeHealthProvider nodeHealthProvider = mock(NodeHealthProvider.class);
+ private SharedHealthState sharedHealthState = mock(SharedHealthState.class);
+ private HealthStateRefresher underTest = new HealthStateRefresher(executorService, nodeHealthProvider, sharedHealthState);
+
+ @Test
+ public void start_adds_runnable_with_10_second_delay_and_initial_delay_putting_NodeHealth_from_provider_into_SharedHealthState() {
+ ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
+ NodeHealth[] nodeHealths = {
+ testSupport.randomNodeHealth(),
+ testSupport.randomNodeHealth(),
+ testSupport.randomNodeHealth()
+ };
+ Error expected = new Error("Simulating exception raised by NodeHealthProvider");
+ when(nodeHealthProvider.get())
+ .thenReturn(nodeHealths[0])
+ .thenReturn(nodeHealths[1])
+ .thenReturn(nodeHealths[2])
+ .thenThrow(expected);
+
+ underTest.start();
+
+ verify(executorService).scheduleWithFixedDelay(runnableCaptor.capture(), eq(1L), eq(10L), eq(TimeUnit.SECONDS));
+
+ Runnable runnable = runnableCaptor.getValue();
+ runnable.run();
+ runnable.run();
+ runnable.run();
+
+ verify(sharedHealthState).writeMine(nodeHealths[0]);
+ verify(sharedHealthState).writeMine(nodeHealths[1]);
+ verify(sharedHealthState).writeMine(nodeHealths[2]);
+
+ try {
+ runnable.run();
+ assertThat(logTester.logs()).hasSize(1);
+ assertThat(logTester.logs(LoggerLevel.ERROR).iterator().next())
+ .isEqualTo("An error occurred while attempting to refresh HealthState of the current node in the shared state:");
+ } catch (IllegalStateException e) {
+ fail("Runnable should catch any Throwable");
+ }
+ }
+
+ @Test
+ public void stop_has_no_effect() {
+ underTest.stop();
+
+ verifyZeroInteractions(executorService, nodeHealthProvider, sharedHealthState);
+ }
+}
diff --git a/server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeDetailsTest.java b/server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeDetailsTest.java
index 1a1947c9c94..6ce0be03349 100644
--- a/server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeDetailsTest.java
+++ b/server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeDetailsTest.java
@@ -20,10 +20,8 @@
package org.sonar.cluster.health;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
import java.util.Random;
import org.junit.Rule;
import org.junit.Test;
@@ -38,7 +36,8 @@ public class NodeDetailsTest {
public ExpectedException expectedException = ExpectedException.none();
private Random random = new Random();
- private NodeDetails.Type randomType = NodeDetails.Type.values()[random.nextInt(NodeDetails.Type.values().length)];
+ private NodeDetailsTestSupport testSupport = new NodeDetailsTestSupport(random);
+ private NodeDetails.Type randomType = testSupport.randomType();
private NodeDetails.Builder builderUnderTest = newNodeDetailsBuilder();
@Test
@@ -173,7 +172,7 @@ public class NodeDetailsTest {
@Test
public void equals_is_based_on_content() {
- NodeDetails.Builder builder = randomBuilder();
+ NodeDetails.Builder builder = testSupport.randomNodeDetailsBuilder();
NodeDetails underTest = builder.build();
@@ -187,7 +186,7 @@ public class NodeDetailsTest {
@Test
public void hashcode_is_based_on_content() {
- NodeDetails.Builder builder = randomBuilder();
+ NodeDetails.Builder builder = testSupport.randomNodeDetailsBuilder();
NodeDetails underTest = builder.build();
@@ -197,8 +196,8 @@ public class NodeDetailsTest {
@Test
public void NodeDetails_is_Externalizable() throws IOException, ClassNotFoundException {
- NodeDetails source = randomNodeDetails();
- byte[] byteArray = serialize(source);
+ NodeDetails source = testSupport.randomNodeDetails();
+ byte[] byteArray = testSupport.serialize(source);
NodeDetails underTest = (NodeDetails) new ObjectInputStream(new ByteArrayInputStream(byteArray)).readObject();
@@ -245,26 +244,4 @@ public class NodeDetailsTest {
assertThat(underTest.getPort()).isEqualTo(port);
assertThat(underTest.getStarted()).isEqualTo(started);
}
-
- private NodeDetails randomNodeDetails() {
- return randomBuilder()
- .build();
- }
-
- private NodeDetails.Builder randomBuilder() {
- return newNodeDetailsBuilder()
- .setType(randomType)
- .setName(randomAlphanumeric(3))
- .setHost(randomAlphanumeric(10))
- .setPort(1 + random.nextInt(10))
- .setStarted(1 + random.nextInt(666));
- }
-
- private static byte[] serialize(NodeDetails source) throws IOException {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(out)) {
- objectOutputStream.writeObject(source);
- }
- return out.toByteArray();
- }
}
diff --git a/server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeDetailsTestSupport.java b/server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeDetailsTestSupport.java
new file mode 100644
index 00000000000..99b0011f6e9
--- /dev/null
+++ b/server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeDetailsTestSupport.java
@@ -0,0 +1,89 @@
+/*
+ * 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.cluster.health;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.ObjectOutputStream;
+import java.util.Random;
+import java.util.stream.IntStream;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.sonar.cluster.health.NodeDetails.newNodeDetailsBuilder;
+import static org.sonar.cluster.health.NodeHealth.newNodeHealthBuilder;
+
+public class NodeDetailsTestSupport {
+ private final Random random;
+
+ public NodeDetailsTestSupport() {
+ this(new Random());
+ }
+
+ NodeDetailsTestSupport(Random random) {
+ this.random = random;
+ }
+
+ NodeHealth.Status randomStatus() {
+ return NodeHealth.Status.values()[random.nextInt(NodeHealth.Status.values().length)];
+ }
+
+ NodeHealth randomNodeHealth() {
+ return randomBuilder().build();
+ }
+
+ NodeHealth.Builder randomBuilder() {
+ return randomBuilder(0);
+ }
+
+ NodeHealth.Builder randomBuilder(int minCauseCount) {
+ NodeHealth.Builder builder = newNodeHealthBuilder()
+ .setStatus(randomStatus())
+ .setDetails(randomNodeDetails())
+ .setDate(1 + random.nextInt(33));
+ IntStream.range(0, minCauseCount + random.nextInt(2)).mapToObj(i -> randomAlphanumeric(4)).forEach(builder::addCause);
+ return builder;
+ }
+
+ NodeDetails randomNodeDetails() {
+ return randomNodeDetailsBuilder()
+ .build();
+ }
+
+ NodeDetails.Builder randomNodeDetailsBuilder() {
+ return newNodeDetailsBuilder()
+ .setType(randomType())
+ .setName(randomAlphanumeric(3))
+ .setHost(randomAlphanumeric(10))
+ .setPort(1 + random.nextInt(10))
+ .setStarted(1 + random.nextInt(666));
+ }
+
+ NodeDetails.Type randomType() {
+ return NodeDetails.Type.values()[random.nextInt(NodeDetails.Type.values().length)];
+ }
+
+ static byte[] serialize(Object source) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(out)) {
+ objectOutputStream.writeObject(source);
+ }
+ return out.toByteArray();
+ }
+}
diff --git a/server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeHealthTest.java b/server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeHealthTest.java
index f1a899646ea..0484fa6883d 100644
--- a/server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeHealthTest.java
+++ b/server/sonar-cluster/src/test/java/org/sonar/cluster/health/NodeHealthTest.java
@@ -20,10 +20,8 @@
package org.sonar.cluster.health;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
-import java.io.ObjectOutputStream;
import java.util.Arrays;
import java.util.Random;
import java.util.stream.IntStream;
@@ -33,8 +31,6 @@ 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.cluster.health.NodeDetails.Type;
-import static org.sonar.cluster.health.NodeDetails.newNodeDetailsBuilder;
import static org.sonar.cluster.health.NodeHealth.newNodeHealthBuilder;
public class NodeHealthTest {
@@ -42,7 +38,8 @@ public class NodeHealthTest {
public ExpectedException expectedException = ExpectedException.none();
private Random random = new Random();
- private NodeHealth.Status randomStatus = NodeHealth.Status.values()[random.nextInt(NodeHealth.Status.values().length)];
+ private NodeDetailsTestSupport testSupport = new NodeDetailsTestSupport(random);
+ private NodeHealth.Status randomStatus = testSupport.randomStatus();
private NodeHealth.Builder builderUnderTest = newNodeHealthBuilder();
@Test
@@ -91,7 +88,7 @@ public class NodeHealthTest {
public void build_throws_IAE_if_date_is_less_than_1() {
builderUnderTest
.setStatus(randomStatus)
- .setDetails(randomNodeDetails());
+ .setDetails(testSupport.randomNodeDetails());
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("date must be > 0");
@@ -101,7 +98,7 @@ public class NodeHealthTest {
@Test
public void clearClauses_clears_clauses_of_builder() {
- NodeHealth.Builder underTest = randomBuilder();
+ NodeHealth.Builder underTest = testSupport.randomBuilder();
NodeHealth original = underTest
.addCause(randomAlphanumeric(3))
.build();
@@ -117,12 +114,12 @@ public class NodeHealthTest {
@Test
public void builder_can_be_reused() {
- NodeHealth.Builder builder = randomBuilder(1);
+ NodeHealth.Builder builder = testSupport.randomBuilder(1);
NodeHealth original = builder.build();
NodeHealth second = builder.build();
NodeHealth.Status newRandomStatus = NodeHealth.Status.values()[random.nextInt(NodeHealth.Status.values().length)];
- NodeDetails newNodeDetails = randomNodeDetails();
+ NodeDetails newNodeDetails = testSupport.randomNodeDetails();
long newDate = 1 + random.nextInt(666);
builder
.clearCauses()
@@ -143,7 +140,7 @@ public class NodeHealthTest {
@Test
public void equals_is_based_on_content() {
- NodeHealth.Builder builder = randomBuilder();
+ NodeHealth.Builder builder = testSupport.randomBuilder();
NodeHealth underTest = builder.build();
@@ -157,7 +154,7 @@ public class NodeHealthTest {
@Test
public void hashcode_is_based_on_content() {
- NodeHealth.Builder builder = randomBuilder();
+ NodeHealth.Builder builder = testSupport.randomBuilder();
NodeHealth underTest = builder.build();
@@ -167,8 +164,8 @@ public class NodeHealthTest {
@Test
public void class_is_serializable_with_causes() throws IOException, ClassNotFoundException {
- NodeHealth source = randomBuilder(1).build();
- byte[] bytes = serialize(source);
+ NodeHealth source = testSupport.randomBuilder(1).build();
+ byte[] bytes = testSupport.serialize(source);
NodeHealth underTest = (NodeHealth) new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject();
@@ -179,27 +176,19 @@ public class NodeHealthTest {
public void class_is_serializable_without_causes() throws IOException, ClassNotFoundException {
NodeHealth.Builder builder = newNodeHealthBuilder()
.setStatus(randomStatus)
- .setDetails(randomNodeDetails())
+ .setDetails(testSupport.randomNodeDetails())
.setDate(1 + random.nextInt(999));
NodeHealth source = builder.build();
- byte[] bytes = serialize(source);
+ byte[] bytes = testSupport.serialize(source);
NodeHealth underTest = (NodeHealth) new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject();
assertThat(underTest).isEqualTo(source);
}
- private byte[] serialize(NodeHealth source) throws IOException {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- try (ObjectOutputStream objectOutputStream = new ObjectOutputStream(out)) {
- objectOutputStream.writeObject(source);
- }
- return out.toByteArray();
- }
-
@Test
public void verify_toString() {
- NodeDetails nodeDetails = randomNodeDetails();
+ NodeDetails nodeDetails = testSupport.randomNodeDetails();
int date = 1 + random.nextInt(999);
String cause = randomAlphanumeric(4);
NodeHealth.Builder builder = builderUnderTest
@@ -216,7 +205,7 @@ public class NodeHealthTest {
@Test
public void verify_getters() {
- NodeDetails nodeDetails = randomNodeDetails();
+ NodeDetails nodeDetails = testSupport.randomNodeDetails();
int date = 1 + random.nextInt(999);
NodeHealth.Builder builder = builderUnderTest
.setStatus(randomStatus)
@@ -233,31 +222,4 @@ public class NodeHealthTest {
assertThat(underTest.getCauses()).containsOnly(causes);
}
- private NodeHealth.Builder randomBuilder() {
- return randomBuilder(0);
- }
-
- private NodeHealth.Builder randomBuilder(int minCauseCount) {
- NodeHealth.Builder builder = newNodeHealthBuilder()
- .setStatus(randomStatus)
- .setDetails(randomNodeDetails())
- .setDate(1 + random.nextInt(33));
- IntStream.range(0, minCauseCount + random.nextInt(2)).mapToObj(i -> randomAlphanumeric(4)).forEach(builder::addCause);
- return builder;
- }
-
- private NodeDetails randomNodeDetails() {
- return randomNodeDetailsBuilder()
- .build();
- }
-
- private NodeDetails.Builder randomNodeDetailsBuilder() {
- return newNodeDetailsBuilder()
- .setType(Type.values()[random.nextInt(Type.values().length)])
- .setName(randomAlphanumeric(3))
- .setHost(randomAlphanumeric(10))
- .setPort(1 + random.nextInt(10))
- .setStarted(1 + random.nextInt(666));
- }
-
}
diff --git a/server/sonar-server/pom.xml b/server/sonar-server/pom.xml
index 95dae9f72e7..747da2c4c96 100644
--- a/server/sonar-server/pom.xml
+++ b/server/sonar-server/pom.xml
@@ -85,8 +85,14 @@
</exclusions>
</dependency>
<dependency>
- <groupId>org.sonarsource.update-center</groupId>
- <artifactId>sonar-update-center-common</artifactId>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sonar-process</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sonar-cluster</artifactId>
+ <version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
@@ -113,6 +119,20 @@
</exclusions>
</dependency>
<dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sonar-ws</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>sonar-plugin-bridge</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.sonarsource.update-center</groupId>
+ <artifactId>sonar-update-center-common</artifactId>
+ </dependency>
+ <dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
</dependency>
@@ -176,20 +196,11 @@
<artifactId>protobuf-java</artifactId>
</dependency>
<dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>sonar-ws</artifactId>
- <version>${project.version}</version>
- </dependency>
- <dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
- <groupId>${project.groupId}</groupId>
- <artifactId>sonar-plugin-bridge</artifactId>
- </dependency>
- <dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
</dependency>
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/HealthStateRefresherExecutorServiceImpl.java b/server/sonar-server/src/main/java/org/sonar/server/health/HealthStateRefresherExecutorServiceImpl.java
new file mode 100644
index 00000000000..472a6565ede
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/HealthStateRefresherExecutorServiceImpl.java
@@ -0,0 +1,38 @@
+/*
+ * 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.util.concurrent.ThreadFactoryBuilder;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import org.sonar.cluster.health.HealthStateRefresherExecutorService;
+import org.sonar.server.util.AbstractStoppableScheduledExecutorServiceImpl;
+
+public class HealthStateRefresherExecutorServiceImpl
+ extends AbstractStoppableScheduledExecutorServiceImpl<ScheduledExecutorService>
+ implements HealthStateRefresherExecutorService {
+ public HealthStateRefresherExecutorServiceImpl() {
+ super(Executors.newSingleThreadScheduledExecutor(
+ new ThreadFactoryBuilder()
+ .setDaemon(false)
+ .setNameFormat("health_state_refresh-%d")
+ .build()));
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/NodeHealthModule.java b/server/sonar-server/src/main/java/org/sonar/server/health/NodeHealthModule.java
new file mode 100644
index 00000000000..c08daf0f934
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/NodeHealthModule.java
@@ -0,0 +1,35 @@
+/*
+ * 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.cluster.health.HealthStateRefresher;
+import org.sonar.cluster.health.SharedHealthStateImpl;
+import org.sonar.core.platform.Module;
+
+public class NodeHealthModule extends Module {
+ @Override
+ protected void configureModule() {
+ add(
+ NodeHealthProviderImpl.class,
+ HealthStateRefresherExecutorServiceImpl.class,
+ HealthStateRefresher.class,
+ SharedHealthStateImpl.class);
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/health/NodeHealthProviderImpl.java b/server/sonar-server/src/main/java/org/sonar/server/health/NodeHealthProviderImpl.java
new file mode 100644
index 00000000000..0c3b263c752
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/health/NodeHealthProviderImpl.java
@@ -0,0 +1,88 @@
+/*
+ * 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.Date;
+import java.util.function.Supplier;
+import org.sonar.NetworkUtils;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.platform.Server;
+import org.sonar.cluster.health.NodeDetails;
+import org.sonar.cluster.health.NodeHealth;
+import org.sonar.cluster.health.NodeHealthProvider;
+
+import static java.lang.String.format;
+import static org.sonar.cluster.ClusterProperties.CLUSTER_NODE_HOST;
+import static org.sonar.cluster.ClusterProperties.CLUSTER_NODE_NAME;
+import static org.sonar.cluster.ClusterProperties.CLUSTER_NODE_PORT;
+import static org.sonar.cluster.health.NodeDetails.newNodeDetailsBuilder;
+import static org.sonar.cluster.health.NodeHealth.newNodeHealthBuilder;
+
+public class NodeHealthProviderImpl implements NodeHealthProvider {
+ private final HealthChecker healthChecker;
+ private final NodeHealth.Builder nodeHealthBuilder;
+ private final NodeDetails nodeDetails;
+
+ public NodeHealthProviderImpl(Configuration configuration, HealthChecker healthChecker, Server server, NetworkUtils networkUtils) {
+ this.healthChecker = healthChecker;
+ this.nodeHealthBuilder = newNodeHealthBuilder();
+ this.nodeDetails = newNodeDetailsBuilder()
+ .setName(computeName(configuration))
+ .setType(NodeDetails.Type.APPLICATION)
+ .setHost(computeHost(configuration, networkUtils))
+ .setPort(computePort(configuration))
+ .setStarted(server.getStartedAt().getTime())
+ .build();
+ }
+
+ private static String computeName(Configuration configuration) {
+ return configuration.get(CLUSTER_NODE_NAME)
+ .orElseThrow(missingPropertyISE(CLUSTER_NODE_NAME));
+ }
+
+ private static String computeHost(Configuration configuration, NetworkUtils networkUtils) {
+ return configuration.get(CLUSTER_NODE_HOST)
+ .filter(s -> !s.isEmpty())
+ .orElseGet(networkUtils::getHostname);
+ }
+
+ private static int computePort(Configuration configuration) {
+ return configuration.getInt(CLUSTER_NODE_PORT)
+ .orElseThrow(missingPropertyISE(CLUSTER_NODE_PORT));
+ }
+
+ private static Supplier<IllegalStateException> missingPropertyISE(String propertyName) {
+ return () -> new IllegalStateException(format("Property %s is not defined", propertyName));
+ }
+
+ @Override
+ public NodeHealth get() {
+ Health nodeHealth = healthChecker.checkNode();
+ this.nodeHealthBuilder
+ .clearCauses()
+ .setStatus(NodeHealth.Status.valueOf(nodeHealth.getStatus().name()));
+ nodeHealth.getCauses().forEach(this.nodeHealthBuilder::addCause);
+
+ return this.nodeHealthBuilder
+ .setDate(new Date().getTime())
+ .setDetails(nodeDetails)
+ .build();
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
index 3941da73054..7b7fc68f854 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel1.java
@@ -21,7 +21,7 @@ package org.sonar.server.platform.platformlevel;
import java.util.Properties;
import javax.annotation.Nullable;
-import org.sonar.db.DBSessionsImpl;
+import org.sonar.NetworkUtils;
import org.sonar.api.SonarQubeSide;
import org.sonar.api.SonarQubeVersion;
import org.sonar.api.internal.ApiVersion;
@@ -32,6 +32,7 @@ import org.sonar.api.utils.internal.TempFolderCleaner;
import org.sonar.core.config.ConfigurationProvider;
import org.sonar.core.config.CorePropertyDefinitions;
import org.sonar.core.util.UuidFactoryImpl;
+import org.sonar.db.DBSessionsImpl;
import org.sonar.db.DaoModule;
import org.sonar.db.DatabaseChecker;
import org.sonar.db.DbClient;
@@ -84,6 +85,7 @@ public class PlatformLevel1 extends PlatformLevel {
ProcessCommandWrapperImpl.class,
RestartFlagHolderImpl.class,
UuidFactoryImpl.INSTANCE,
+ NetworkUtils.INSTANCE,
UrlSettings.class,
EmbeddedDatabaseFactory.class,
LogbackHelper.class,
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 bda21231f0a..f6c7a77d648 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
@@ -31,6 +31,7 @@ import org.sonar.api.rules.XMLRuleParser;
import org.sonar.api.server.rule.RulesDefinitionXmlLoader;
import org.sonar.ce.CeModule;
import org.sonar.ce.settings.ProjectConfigurationFactory;
+import org.sonar.cluster.localclient.HazelcastLocalClient;
import org.sonar.core.component.DefaultResourceTypes;
import org.sonar.core.timemachine.Periods;
import org.sonar.server.authentication.AuthenticationModule;
@@ -61,6 +62,7 @@ import org.sonar.server.es.metadata.MetadataIndex;
import org.sonar.server.es.metadata.MetadataIndexDefinition;
import org.sonar.server.event.NewAlerts;
import org.sonar.server.favorite.FavoriteModule;
+import org.sonar.server.health.NodeHealthModule;
import org.sonar.server.issue.AddTagsAction;
import org.sonar.server.issue.AssignAction;
import org.sonar.server.issue.CommentAction;
@@ -243,6 +245,10 @@ public class PlatformLevel4 extends PlatformLevel {
MetadataIndex.class,
EsDbCompatibilityImpl.class);
+ addIfCluster(
+ HazelcastLocalClient.class,
+ NodeHealthModule.class);
+
add(
PluginDownloader.class,
DeprecatedViews.class,
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 2dad8eaf37f..871a9314ee6 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
@@ -27,17 +27,16 @@ import org.sonar.server.health.HealthCheckerImpl;
import org.sonar.server.health.WebServerStatusNodeCheck;
public class HealthActionModule extends Module {
+
@Override
protected void configureModule() {
- add(
- // NodeHealthCheck implementations
- WebServerStatusNodeCheck.class,
+ // NodeHealthCheck implementations
+ add(WebServerStatusNodeCheck.class,
DbConnectionNodeCheck.class,
EsStatusNodeCheck.class,
- CeStatusNodeCheck.class,
-
- HealthCheckerImpl.class,
+ CeStatusNodeCheck.class);
+ add(HealthCheckerImpl.class,
HealthAction.class);
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/health/NodeHealthModuleTest.java b/server/sonar-server/src/test/java/org/sonar/server/health/NodeHealthModuleTest.java
new file mode 100644
index 00000000000..295d3c70eb9
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/health/NodeHealthModuleTest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.Collection;
+import java.util.Date;
+import java.util.List;
+import java.util.Random;
+import java.util.stream.Collectors;
+import org.junit.Test;
+import org.picocontainer.ComponentAdapter;
+import org.sonar.NetworkUtils;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.platform.Server;
+import org.sonar.api.utils.System2;
+import org.sonar.cluster.health.SharedHealthStateImpl;
+import org.sonar.cluster.localclient.HazelcastClient;
+import org.sonar.core.platform.ComponentContainer;
+
+import static java.lang.String.valueOf;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class NodeHealthModuleTest {
+ private Random random = new Random();
+ private MapSettings mapSettings = new MapSettings();
+ private NodeHealthModule underTest = new NodeHealthModule();
+
+ @Test
+ public void no_broken_dependencies() {
+ ComponentContainer container = new ComponentContainer();
+ Server server = mock(Server.class);
+ NetworkUtils networkUtils = mock(NetworkUtils.class);
+ // settings required by NodeHealthProvider
+ mapSettings.setProperty("sonar.cluster.node.name", randomAlphanumeric(3));
+ mapSettings.setProperty("sonar.cluster.node.port", valueOf(1 + random.nextInt(10)));
+ when(server.getStartedAt()).thenReturn(new Date());
+ when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(12));
+ // upper level dependencies
+ container.add(
+ mock(System2.class),
+ mapSettings.asConfig(),
+ server,
+ networkUtils,
+ mock(HazelcastClient.class));
+ // HealthAction dependencies
+ container.add(mock(HealthChecker.class));
+
+ underTest.configure(container);
+
+ container.startComponents();
+ }
+
+ @Test
+ public void provides_implementation_of_SharedHealthState() {
+ ComponentContainer container = new ComponentContainer();
+
+ underTest.configure(container);
+
+ assertThat(classesAddedToContainer(container))
+ .contains(SharedHealthStateImpl.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/health/NodeHealthProviderImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/health/NodeHealthProviderImplTest.java
new file mode 100644
index 00000000000..02a0df617c8
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/health/NodeHealthProviderImplTest.java
@@ -0,0 +1,242 @@
+/*
+ * 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.Date;
+import java.util.Random;
+import java.util.stream.IntStream;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.sonar.NetworkUtils;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.api.platform.Server;
+import org.sonar.cluster.health.NodeDetails;
+import org.sonar.cluster.health.NodeHealth;
+
+import static org.apache.commons.lang.RandomStringUtils.randomAlphabetic;
+import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.cluster.ClusterProperties.CLUSTER_NODE_HOST;
+import static org.sonar.cluster.ClusterProperties.CLUSTER_NODE_NAME;
+import static org.sonar.cluster.ClusterProperties.CLUSTER_NODE_PORT;
+
+public class NodeHealthProviderImplTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ private final Random random = new Random();
+ private MapSettings mapSettings = new MapSettings();
+ private HealthChecker healthChecker = mock(HealthChecker.class);
+ private Server server = mock(Server.class);
+ private NetworkUtils networkUtils = mock(NetworkUtils.class);
+
+ @Test
+ public void constructor_throws_ISE_if_node_name_property_is_not_set() {
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Property sonar.cluster.node.name is not defined");
+
+ new NodeHealthProviderImpl(mapSettings.asConfig(), healthChecker, server, networkUtils);
+ }
+
+ @Test
+ public void constructor_thows_NPE_if_NetworkUtils_getHostname_returns_null() {
+ mapSettings.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3));
+
+ expectedException.expect(NullPointerException.class);
+
+ new NodeHealthProviderImpl(mapSettings.asConfig(), healthChecker, server, networkUtils);
+ }
+
+ @Test
+ public void constructor_throws_ISE_if_node_port_property_is_not_set() {
+ mapSettings.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3));
+ when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(23));
+
+ expectedException.expect(IllegalStateException.class);
+ expectedException.expectMessage("Property sonar.cluster.node.port is not defined");
+
+ new NodeHealthProviderImpl(mapSettings.asConfig(), healthChecker, server, networkUtils);
+ }
+
+ @Test
+ public void constructor_throws_NPE_is_Server_getStartedAt_is_null() {
+ setRequiredPropertiesForConstructor();
+
+ expectedException.expect(NullPointerException.class);
+
+ new NodeHealthProviderImpl(mapSettings.asConfig(), healthChecker, server, networkUtils);
+ }
+
+ @Test
+ public void get_returns_HEALTH_status_and_causes_from_HealthChecker_checkNode() {
+ setRequiredPropertiesForConstructor();
+ setStartedAt();
+ when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(4));
+ Health.Status randomStatus = Health.Status.values()[random.nextInt(Health.Status.values().length)];
+ String[] expected = IntStream.range(0, random.nextInt(4)).mapToObj(s -> randomAlphabetic(55)).toArray(String[]::new);
+ Health.Builder healthBuilder = Health.newHealthCheckBuilder()
+ .setStatus(randomStatus);
+ Arrays.stream(expected).forEach(healthBuilder::addCause);
+ when(healthChecker.checkNode()).thenReturn(healthBuilder.build());
+ NodeHealthProviderImpl underTest = new NodeHealthProviderImpl(mapSettings.asConfig(), healthChecker, server, networkUtils);
+
+ NodeHealth nodeHealth = underTest.get();
+
+ assertThat(nodeHealth.getStatus().name()).isEqualTo(randomStatus.name());
+ assertThat(nodeHealth.getCauses()).containsOnly(expected);
+ }
+
+ @Test
+ public void get_returns_APPLICATION_type() {
+ setRequiredPropertiesForConstructor();
+ setStartedAt();
+ when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(23));
+ when(healthChecker.checkNode()).thenReturn(Health.newHealthCheckBuilder()
+ .setStatus(Health.Status.values()[random.nextInt(Health.Status.values().length)])
+ .build());
+ NodeHealthProviderImpl underTest = new NodeHealthProviderImpl(mapSettings.asConfig(), healthChecker, server, networkUtils);
+
+ NodeHealth nodeHealth = underTest.get();
+
+ assertThat(nodeHealth.getDetails().getType()).isEqualTo(NodeDetails.Type.APPLICATION);
+ }
+
+ @Test
+ public void get_returns_name_and_port_from_properties_at_constructor_time() {
+ String name = randomAlphanumeric(3);
+ int port = 1 + random.nextInt(4);
+ mapSettings.setProperty(CLUSTER_NODE_NAME, name);
+ mapSettings.setProperty(CLUSTER_NODE_PORT, port);
+ setStartedAt();
+ when(healthChecker.checkNode()).thenReturn(Health.newHealthCheckBuilder()
+ .setStatus(Health.Status.values()[random.nextInt(Health.Status.values().length)])
+ .build());
+ when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(3));
+ NodeHealthProviderImpl underTest = new NodeHealthProviderImpl(mapSettings.asConfig(), healthChecker, server, networkUtils);
+
+ NodeHealth nodeHealth = underTest.get();
+
+ assertThat(nodeHealth.getDetails().getName()).isEqualTo(name);
+ assertThat(nodeHealth.getDetails().getPort()).isEqualTo(port);
+
+ // change values in properties
+ setRequiredPropertiesForConstructor();
+
+ NodeHealth newNodeHealth = underTest.get();
+
+ assertThat(newNodeHealth.getDetails().getName()).isEqualTo(name);
+ assertThat(newNodeHealth.getDetails().getPort()).isEqualTo(port);
+ }
+
+ @Test
+ public void get_returns_host_from_property_if_set_at_constructor_time() {
+ String host = randomAlphanumeric(4);
+ mapSettings.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3));
+ mapSettings.setProperty(CLUSTER_NODE_PORT, 1 + random.nextInt(4));
+ mapSettings.setProperty(CLUSTER_NODE_HOST, host);
+ setStartedAt();
+ when(healthChecker.checkNode()).thenReturn(Health.newHealthCheckBuilder()
+ .setStatus(Health.Status.values()[random.nextInt(Health.Status.values().length)])
+ .build());
+ NodeHealthProviderImpl underTest = new NodeHealthProviderImpl(mapSettings.asConfig(), healthChecker, server, networkUtils);
+
+ NodeHealth nodeHealth = underTest.get();
+
+ assertThat(nodeHealth.getDetails().getHost()).isEqualTo(host);
+
+ // change values in properties
+ mapSettings.setProperty(CLUSTER_NODE_HOST, randomAlphanumeric(66));
+
+ NodeHealth newNodeHealth = underTest.get();
+
+ assertThat(newNodeHealth.getDetails().getHost()).isEqualTo(host);
+ }
+
+ @Test
+ public void get_returns_hostname_from_NetworkUtils_if_property_is_not_set_at_constructor_time() {
+ getReturnsHostnameFromNetworkUtils(null);
+ }
+
+ @Test
+ public void get_returns_hostname_from_NetworkUtils_if_property_is_empty_at_constructor_time() {
+ getReturnsHostnameFromNetworkUtils(random.nextBoolean() ? "" : " ");
+ }
+
+ private void getReturnsHostnameFromNetworkUtils(String hostPropertyValue) {
+ String host = randomAlphanumeric(3);
+ setRequiredPropertiesForConstructor();
+ if (hostPropertyValue != null) {
+ mapSettings.setProperty(CLUSTER_NODE_HOST, hostPropertyValue);
+ }
+ setStartedAt();
+ when(healthChecker.checkNode()).thenReturn(Health.newHealthCheckBuilder()
+ .setStatus(Health.Status.values()[random.nextInt(Health.Status.values().length)])
+ .build());
+ when(networkUtils.getHostname()).thenReturn(host);
+ NodeHealthProviderImpl underTest = new NodeHealthProviderImpl(mapSettings.asConfig(), healthChecker, server, networkUtils);
+
+ NodeHealth nodeHealth = underTest.get();
+
+ assertThat(nodeHealth.getDetails().getHost()).isEqualTo(host);
+
+ // change hostname
+ when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(4));
+
+ NodeHealth newNodeHealth = underTest.get();
+
+ assertThat(newNodeHealth.getDetails().getHost()).isEqualTo(host);
+ }
+
+ @Test
+ public void get_returns_started_from_server_startedAt_at_constructor_time() {
+ setRequiredPropertiesForConstructor();
+ when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(4));
+ Date date = new Date();
+ when(server.getStartedAt()).thenReturn(date);
+ when(healthChecker.checkNode()).thenReturn(Health.newHealthCheckBuilder()
+ .setStatus(Health.Status.values()[random.nextInt(Health.Status.values().length)])
+ .build());
+ NodeHealthProviderImpl underTest = new NodeHealthProviderImpl(mapSettings.asConfig(), healthChecker, server, networkUtils);
+
+ NodeHealth nodeHealth = underTest.get();
+
+ assertThat(nodeHealth.getDetails().getStarted()).isEqualTo(date.getTime());
+
+ // change startedAt value
+ setStartedAt();
+
+ NodeHealth newNodeHealth = underTest.get();
+
+ assertThat(newNodeHealth.getDetails().getStarted()).isEqualTo(date.getTime());
+ }
+
+ private void setStartedAt() {
+ when(server.getStartedAt()).thenReturn(new Date());
+ }
+
+ private void setRequiredPropertiesForConstructor() {
+ mapSettings.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3));
+ mapSettings.setProperty(CLUSTER_NODE_PORT, 1 + random.nextInt(4));
+ }
+}