diff options
author | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2017-08-31 17:12:00 +0200 |
---|---|---|
committer | Sébastien Lesaint <sebastien.lesaint@sonarsource.com> | 2017-09-13 15:50:51 +0200 |
commit | fa0fe58b0561cac895939487f11c22c92f18d03f (patch) | |
tree | 54fb2eddd39747196f2871484b1afd379ae27901 | |
parent | 84422d2ca5f4dcec1aa0ac17486a532574d676e1 (diff) | |
download | sonarqube-fa0fe58b0561cac895939487f11c22c92f18d03f.tar.gz sonarqube-fa0fe58b0561cac895939487f11c22c92f18d03f.zip |
SONAR-9741 search nodes share startup health status
13 files changed, 338 insertions, 78 deletions
diff --git a/server/sonar-main/src/main/java/org/sonar/application/AppStateFactory.java b/server/sonar-main/src/main/java/org/sonar/application/AppStateFactory.java index 196aa2a9fa0..8f5cfbfe780 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/AppStateFactory.java +++ b/server/sonar-main/src/main/java/org/sonar/application/AppStateFactory.java @@ -19,7 +19,7 @@ */ package org.sonar.application; -import org.sonar.application.cluster.AppStateClusterImpl; +import org.sonar.application.cluster.ClusterAppStateImpl; import org.sonar.application.config.AppSettings; import org.sonar.application.config.ClusterSettings; @@ -32,6 +32,6 @@ public class AppStateFactory { } public AppState create() { - return ClusterSettings.isClusterEnabled(settings) ? new AppStateClusterImpl(settings) : new AppStateImpl(); + return ClusterSettings.isClusterEnabled(settings) ? new ClusterAppStateImpl(settings) : new AppStateImpl(); } } diff --git a/server/sonar-main/src/main/java/org/sonar/application/SchedulerImpl.java b/server/sonar-main/src/main/java/org/sonar/application/SchedulerImpl.java index 621cce82b3b..3268bb249c6 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/SchedulerImpl.java +++ b/server/sonar-main/src/main/java/org/sonar/application/SchedulerImpl.java @@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.sonar.NetworkUtils; import org.sonar.api.utils.System2; import org.sonar.application.cluster.ClusterAppState; import org.sonar.application.config.AppSettings; @@ -151,7 +152,7 @@ public class SchedulerImpl implements Scheduler, ProcessEventListener, ProcessLi ClusterAppState clusterAppState = (ClusterAppState) appState; this.healthStateSharing = new HealthStateSharingImpl( clusterAppState.getHazelcastClient(), - new SearchNodeHealthProvider(settings.getProps(), System2.INSTANCE)); + new SearchNodeHealthProvider(settings.getProps(), System2.INSTANCE, clusterAppState, NetworkUtils.INSTANCE)); this.healthStateSharing.start(); } } diff --git a/server/sonar-main/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java b/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterAppStateImpl.java index 717f41b287b..9742941f132 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/cluster/AppStateClusterImpl.java +++ b/server/sonar-main/src/main/java/org/sonar/application/cluster/ClusterAppStateImpl.java @@ -36,13 +36,13 @@ import static org.sonar.cluster.ClusterProperties.CLUSTER_ENABLED; import static org.sonar.cluster.ClusterProperties.CLUSTER_LOCALENDPOINT; import static org.sonar.cluster.ClusterProperties.CLUSTER_MEMBERUUID; -public class AppStateClusterImpl implements ClusterAppState { - private static Logger LOGGER = LoggerFactory.getLogger(AppStateClusterImpl.class); +public class ClusterAppStateImpl implements ClusterAppState { + private static Logger LOGGER = LoggerFactory.getLogger(ClusterAppStateImpl.class); private final Map<ProcessId, Boolean> localProcesses = new EnumMap<>(ProcessId.class); private final HazelcastCluster hazelcastCluster; - public AppStateClusterImpl(AppSettings appSettings) { + public ClusterAppStateImpl(AppSettings appSettings) { if (!appSettings.getProps().valueAsBoolean(CLUSTER_ENABLED)) { throw new IllegalStateException("Cluster is not enabled on this instance"); } @@ -123,7 +123,7 @@ public class AppStateClusterImpl implements ClusterAppState { * @param logger */ static void setLogger(Logger logger) { - AppStateClusterImpl.LOGGER = logger; + ClusterAppStateImpl.LOGGER = logger; } } diff --git a/server/sonar-main/src/main/java/org/sonar/application/health/SearchNodeHealthProvider.java b/server/sonar-main/src/main/java/org/sonar/application/health/SearchNodeHealthProvider.java index 83f424217e1..d1e184ad436 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/health/SearchNodeHealthProvider.java +++ b/server/sonar-main/src/main/java/org/sonar/application/health/SearchNodeHealthProvider.java @@ -19,37 +19,54 @@ */ package org.sonar.application.health; -import java.util.Random; +import org.sonar.NetworkUtils; import org.sonar.api.utils.System2; -import org.sonar.cluster.ClusterProperties; +import org.sonar.application.cluster.ClusterAppState; import org.sonar.cluster.health.NodeDetails; import org.sonar.cluster.health.NodeHealth; import org.sonar.cluster.health.NodeHealthProvider; +import org.sonar.process.ProcessId; import org.sonar.process.Props; +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 SearchNodeHealthProvider implements NodeHealthProvider { private final System2 system2; + private final ClusterAppState clusterAppState; private final NodeDetails nodeDetails; - public SearchNodeHealthProvider(Props props, System2 system2) { + public SearchNodeHealthProvider(Props props, System2 system2, ClusterAppState clusterAppState, NetworkUtils networkUtils) { this.system2 = system2; + this.clusterAppState = clusterAppState; this.nodeDetails = NodeDetails.newNodeDetailsBuilder() .setType(NodeDetails.Type.SEARCH) - .setName(props.nonNullValue(ClusterProperties.CLUSTER_NAME) + new Random().nextInt(999)) - // TODO read sonar.cluster.node.host - .setHost("host hardcoded for now") + .setName(props.nonNullValue(CLUSTER_NODE_NAME)) + .setHost(getHost(props, networkUtils)) .setPort(Integer.valueOf(props.nonNullValue(CLUSTER_NODE_PORT))) - // TODO is now good enough? .setStarted(system2.now()) .build(); } + private static String getHost(Props props, NetworkUtils networkUtils) { + String host = props.value(CLUSTER_NODE_HOST); + if (host != null && !host.isEmpty()) { + return host; + } + return networkUtils.getHostname(); + } + @Override public NodeHealth get() { - return NodeHealth.newNodeHealthBuilder() - .setStatus(NodeHealth.Status.GREEN) + NodeHealth.Builder builder = NodeHealth.newNodeHealthBuilder(); + if (clusterAppState.isOperational(ProcessId.ELASTICSEARCH, true)) { + builder.setStatus(NodeHealth.Status.GREEN); + } else { + builder.setStatus(NodeHealth.Status.RED) + .addCause("Elasticsearch is not operational"); + } + return builder .setDetails(nodeDetails) .setDate(system2.now()) .build(); diff --git a/server/sonar-main/src/test/java/org/sonar/application/AppStateFactoryTest.java b/server/sonar-main/src/test/java/org/sonar/application/AppStateFactoryTest.java index 3998aabe983..10b9d0d6d3c 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/AppStateFactoryTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/AppStateFactoryTest.java @@ -20,7 +20,7 @@ package org.sonar.application; import org.junit.Test; -import org.sonar.application.cluster.AppStateClusterImpl; +import org.sonar.application.cluster.ClusterAppStateImpl; import org.sonar.application.config.TestAppSettings; import static org.assertj.core.api.Assertions.assertThat; @@ -40,8 +40,8 @@ public class AppStateFactoryTest { settings.set(CLUSTER_NAME, "foo"); AppState appState = underTest.create(); - assertThat(appState).isInstanceOf(AppStateClusterImpl.class); - appState.close(); + assertThat(appState).isInstanceOf(ClusterAppStateImpl.class); + ((ClusterAppStateImpl) appState).close(); } @Test diff --git a/server/sonar-main/src/test/java/org/sonar/application/SchedulerImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/SchedulerImplTest.java index 91bd75d1ba4..f419f4d0e17 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/SchedulerImplTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/SchedulerImplTest.java @@ -24,6 +24,7 @@ import java.io.InputStream; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; +import java.util.Random; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.junit.After; @@ -40,6 +41,7 @@ import org.mockito.Mockito; import org.sonar.application.config.TestAppSettings; import org.sonar.application.process.ProcessLauncher; import org.sonar.application.process.ProcessMonitor; +import org.sonar.cluster.localclient.HazelcastClient; import org.sonar.process.ProcessId; import org.sonar.process.command.AbstractCommand; import org.sonar.process.command.CommandFactory; @@ -47,12 +49,16 @@ import org.sonar.process.command.EsCommand; import org.sonar.process.command.JavaCommand; import static java.util.Collections.synchronizedList; +import static org.apache.commons.lang.RandomStringUtils.randomAlphanumeric; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; import static org.sonar.cluster.ClusterProperties.CLUSTER_ENABLED; +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.ClusterProperties.CLUSTER_NODE_TYPE; import static org.sonar.process.ProcessId.COMPUTE_ENGINE; import static org.sonar.process.ProcessId.ELASTICSEARCH; @@ -77,6 +83,8 @@ public class SchedulerImplTest { private TestCommandFactory javaCommandFactory = new TestCommandFactory(); private TestProcessLauncher processLauncher = new TestProcessLauncher(); private TestAppState appState = new TestAppState(); + private HazelcastClient hazelcastClient = mock(HazelcastClient.class); + private TestClusterAppState clusterAppState = new TestClusterAppState(hazelcastClient); private List<ProcessId> orderedStops = synchronizedList(new ArrayList<>()); @Before @@ -95,7 +103,7 @@ public class SchedulerImplTest { @Test public void start_and_stop_sequence_of_ES_WEB_CE_in_order() throws Exception { - SchedulerImpl underTest = newScheduler(); + SchedulerImpl underTest = newScheduler(false); underTest.schedule(); // elasticsearch does not have preconditions to start @@ -105,7 +113,7 @@ public class SchedulerImplTest { // elasticsearch becomes operational -> web leader is starting es.operational = true; - waitForAppStateOperational(ELASTICSEARCH); + waitForAppStateOperational(appState, ELASTICSEARCH); TestProcess web = processLauncher.waitForProcess(WEB_SERVER); assertThat(web.isAlive()).isTrue(); assertThat(processLauncher.processes).hasSize(2); @@ -113,7 +121,7 @@ public class SchedulerImplTest { // web becomes operational -> CE is starting web.operational = true; - waitForAppStateOperational(WEB_SERVER); + waitForAppStateOperational(appState, WEB_SERVER); TestProcess ce = processLauncher.waitForProcess(COMPUTE_ENGINE); assertThat(ce.isAlive()).isTrue(); assertThat(processLauncher.processes).hasSize(3); @@ -148,7 +156,7 @@ public class SchedulerImplTest { @Test public void all_processes_are_stopped_if_one_process_fails_to_start() throws Exception { - SchedulerImpl underTest = newScheduler(); + SchedulerImpl underTest = newScheduler(false); processLauncher.makeStartupFail = COMPUTE_ENGINE; underTest.schedule(); @@ -237,7 +245,8 @@ public class SchedulerImplTest { public void search_node_starts_only_elasticsearch() throws Exception { settings.set(CLUSTER_ENABLED, "true"); settings.set(CLUSTER_NODE_TYPE, "search"); - SchedulerImpl underTest = newScheduler(); + addRequiredNodeProperties(); + SchedulerImpl underTest = newScheduler(true); underTest.schedule(); processLauncher.waitForProcessAlive(ProcessId.ELASTICSEARCH); @@ -248,10 +257,10 @@ public class SchedulerImplTest { @Test public void application_node_starts_only_web_and_ce() throws Exception { - appState.setOperational(ProcessId.ELASTICSEARCH); + clusterAppState.setOperational(ProcessId.ELASTICSEARCH); settings.set(CLUSTER_ENABLED, "true"); settings.set(CLUSTER_NODE_TYPE, "application"); - SchedulerImpl underTest = newScheduler(); + SchedulerImpl underTest = newScheduler(true); underTest.schedule(); TestProcess web = processLauncher.waitForProcessAlive(WEB_SERVER); @@ -265,12 +274,13 @@ public class SchedulerImplTest { @Test public void search_node_starts_even_if_web_leader_is_not_yet_operational() throws Exception { // leader takes the lock, so underTest won't get it - assertThat(appState.tryToLockWebLeader()).isTrue(); + assertThat(clusterAppState.tryToLockWebLeader()).isTrue(); - appState.setOperational(ProcessId.ELASTICSEARCH); + clusterAppState.setOperational(ProcessId.ELASTICSEARCH); settings.set(CLUSTER_ENABLED, "true"); settings.set(CLUSTER_NODE_TYPE, "search"); - SchedulerImpl underTest = newScheduler(); + addRequiredNodeProperties(); + SchedulerImpl underTest = newScheduler(true); underTest.schedule(); processLauncher.waitForProcessAlive(ProcessId.ELASTICSEARCH); @@ -282,19 +292,18 @@ public class SchedulerImplTest { @Test public void web_follower_starts_only_when_web_leader_is_operational() throws Exception { // leader takes the lock, so underTest won't get it - assertThat(appState.tryToLockWebLeader()).isTrue(); - appState.setOperational(ProcessId.ELASTICSEARCH); + assertThat(clusterAppState.tryToLockWebLeader()).isTrue(); + clusterAppState.setOperational(ProcessId.ELASTICSEARCH); settings.set(CLUSTER_ENABLED, "true"); settings.set(CLUSTER_NODE_TYPE, "application"); - SchedulerImpl underTest = newScheduler(); + SchedulerImpl underTest = newScheduler(true); underTest.schedule(); assertThat(processLauncher.processes).hasSize(0); // leader becomes operational -> follower can start - appState.setOperational(WEB_SERVER); - + clusterAppState.setOperational(WEB_SERVER); processLauncher.waitForProcessAlive(WEB_SERVER); processLauncher.waitForProcessAlive(COMPUTE_ENGINE); assertThat(processLauncher.processes).hasSize(2); @@ -306,27 +315,27 @@ public class SchedulerImplTest { public void web_server_waits_for_remote_elasticsearch_to_be_started_if_local_es_is_disabled() throws Exception { settings.set(CLUSTER_ENABLED, "true"); settings.set(CLUSTER_NODE_TYPE, "application"); - SchedulerImpl underTest = newScheduler(); + SchedulerImpl underTest = newScheduler(true); underTest.schedule(); // WEB and CE wait for ES to be up assertThat(processLauncher.processes).isEmpty(); // ES becomes operational on another node -> web leader can start - appState.setRemoteOperational(ProcessId.ELASTICSEARCH); + clusterAppState.setRemoteOperational(ProcessId.ELASTICSEARCH); processLauncher.waitForProcessAlive(WEB_SERVER); assertThat(processLauncher.processes).hasSize(1); underTest.terminate(); } - private SchedulerImpl newScheduler() { - return new SchedulerImpl(settings, appReloader, javaCommandFactory, processLauncher, appState) + private SchedulerImpl newScheduler(boolean clustered) { + return new SchedulerImpl(settings, appReloader, javaCommandFactory, processLauncher, clustered ? clusterAppState : appState) .setProcessWatcherDelayMs(1L); } private Scheduler startAll() throws InterruptedException { - SchedulerImpl scheduler = newScheduler(); + SchedulerImpl scheduler = newScheduler(false); scheduler.schedule(); processLauncher.waitForProcess(ELASTICSEARCH).operational = true; processLauncher.waitForProcess(WEB_SERVER).operational = true; @@ -334,7 +343,7 @@ public class SchedulerImplTest { return scheduler; } - private void waitForAppStateOperational(ProcessId id) throws InterruptedException { + private static void waitForAppStateOperational(AppState appState, ProcessId id) throws InterruptedException { while (true) { if (appState.isOperational(id, true)) { return; @@ -343,6 +352,12 @@ public class SchedulerImplTest { } } + private void addRequiredNodeProperties() { + settings.set(CLUSTER_NODE_NAME, randomAlphanumeric(4)); + settings.set(CLUSTER_NODE_HOST, randomAlphanumeric(4)); + settings.set(CLUSTER_NODE_PORT, String.valueOf(1 + new Random().nextInt(999))); + } + private class TestCommandFactory implements CommandFactory { @Override public EsCommand createEsCommand() { diff --git a/server/sonar-main/src/test/java/org/sonar/application/TestAppState.java b/server/sonar-main/src/test/java/org/sonar/application/TestAppState.java index 125ae867d40..27f6c4d5fb0 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/TestAppState.java +++ b/server/sonar-main/src/test/java/org/sonar/application/TestAppState.java @@ -19,7 +19,6 @@ */ package org.sonar.application; -import com.google.common.base.Preconditions; import java.util.ArrayList; import java.util.EnumMap; import java.util.List; @@ -28,25 +27,14 @@ import java.util.Optional; import java.util.concurrent.atomic.AtomicBoolean; import javax.annotation.Nonnull; import org.sonar.NetworkUtils; -import org.sonar.application.cluster.ClusterAppState; -import org.sonar.cluster.localclient.HazelcastClient; import org.sonar.process.ProcessId; -public class TestAppState implements ClusterAppState { +public class TestAppState implements AppState { private final Map<ProcessId, Boolean> localProcesses = new EnumMap<>(ProcessId.class); private final Map<ProcessId, Boolean> remoteProcesses = new EnumMap<>(ProcessId.class); private final List<AppStateListener> listeners = new ArrayList<>(); private final AtomicBoolean webLeaderLocked = new AtomicBoolean(false); - private final HazelcastClient hazelcastClient; - - public TestAppState() { - this(null); - } - - public TestAppState(HazelcastClient hazelcastClient) { - this.hazelcastClient = hazelcastClient; - } @Override public void addListener(@Nonnull AppStateListener listener) { @@ -100,12 +88,6 @@ public class TestAppState implements ClusterAppState { } @Override - public HazelcastClient getHazelcastClient() { - Preconditions.checkState(hazelcastClient != null, "An HazelcastClient should be provided when testing in cluster mode"); - return hazelcastClient; - } - - @Override public void close() { // nothing to do } diff --git a/server/sonar-main/src/test/java/org/sonar/application/TestClusterAppState.java b/server/sonar-main/src/test/java/org/sonar/application/TestClusterAppState.java new file mode 100644 index 00000000000..94d5e664f5c --- /dev/null +++ b/server/sonar-main/src/test/java/org/sonar/application/TestClusterAppState.java @@ -0,0 +1,36 @@ +/* + * 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.application; + +import org.sonar.application.cluster.ClusterAppState; +import org.sonar.cluster.localclient.HazelcastClient; + +public class TestClusterAppState extends TestAppState implements ClusterAppState { + private final HazelcastClient hazelcastClient; + + public TestClusterAppState(HazelcastClient hazelcastClient) { + this.hazelcastClient = hazelcastClient; + } + + @Override + public HazelcastClient getHazelcastClient() { + return hazelcastClient; + } +} diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java index 7e8610c5e80..240db1cbfe0 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/cluster/AppStateClusterImplTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/cluster/ClusterAppStateImplTest.java @@ -46,7 +46,7 @@ import static org.sonar.cluster.ClusterObjectKeys.CLUSTER_NAME; import static org.sonar.cluster.ClusterObjectKeys.SONARQUBE_VERSION; import static org.sonar.cluster.ClusterProperties.CLUSTER_ENABLED; -public class AppStateClusterImplTest { +public class ClusterAppStateImplTest { @Rule public ExpectedException expectedException = ExpectedException.none(); @@ -62,14 +62,14 @@ public class AppStateClusterImplTest { expectedException.expect(IllegalStateException.class); expectedException.expectMessage("Cluster is not enabled on this instance"); - new AppStateClusterImpl(settings); + new ClusterAppStateImpl(settings); } @Test public void tryToLockWebLeader_returns_true_only_for_the_first_call() throws Exception { TestAppSettings settings = newApplicationSettings(); - try (AppStateClusterImpl underTest = new AppStateClusterImpl(settings)) { + try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(settings)) { assertThat(underTest.tryToLockWebLeader()).isEqualTo(true); assertThat(underTest.tryToLockWebLeader()).isEqualTo(false); } @@ -81,9 +81,9 @@ public class AppStateClusterImplTest { TestAppSettings settings = newApplicationSettings(); Logger logger = mock(Logger.class); - AppStateClusterImpl.setLogger(logger); + ClusterAppStateImpl.setLogger(logger); - try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) { + try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) { verify(logger).info( eq("Joined a SonarQube cluster that contains the following hosts : [{}]"), anyString()); @@ -93,7 +93,7 @@ public class AppStateClusterImplTest { @Test public void test_listeners() throws InterruptedException { AppStateListener listener = mock(AppStateListener.class); - try (AppStateClusterImpl underTest = new AppStateClusterImpl(newApplicationSettings())) { + try (ClusterAppStateImpl underTest = new ClusterAppStateImpl(newApplicationSettings())) { underTest.addListener(listener); underTest.setOperational(ProcessId.ELASTICSEARCH); @@ -110,7 +110,7 @@ public class AppStateClusterImplTest { public void registerSonarQubeVersion_publishes_version_on_first_call() { TestAppSettings settings = newApplicationSettings(); - try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) { + try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) { appStateCluster.registerSonarQubeVersion("6.4.1.5"); HazelcastInstance hzInstance = createHazelcastClient(appStateCluster); @@ -126,7 +126,7 @@ public class AppStateClusterImplTest { TestAppSettings settings = newApplicationSettings(); String clusterName = randomAlphanumeric(20); - try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) { + try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) { appStateCluster.registerClusterName(clusterName); HazelcastInstance hzInstance = createHazelcastClient(appStateCluster); @@ -141,7 +141,7 @@ public class AppStateClusterImplTest { public void reset_throws_always_ISE() { TestAppSettings settings = newApplicationSettings(); - try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) { + try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) { expectedException.expect(IllegalStateException.class); expectedException.expectMessage("state reset is not supported in cluster mode"); appStateCluster.reset(); @@ -153,7 +153,7 @@ public class AppStateClusterImplTest { // Now launch an instance that try to be part of the hzInstance cluster TestAppSettings settings = newApplicationSettings(); - try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) { + try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) { // Register first version appStateCluster.registerSonarQubeVersion("1.0.0"); @@ -170,7 +170,7 @@ public class AppStateClusterImplTest { // Now launch an instance that try to be part of the hzInstance cluster TestAppSettings settings = newApplicationSettings(); - try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) { + try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) { // Register first version appStateCluster.registerClusterName("goodClusterName"); diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastClusterTest.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastClusterTest.java index 702359aa2ee..11a77839f35 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastClusterTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastClusterTest.java @@ -244,7 +244,7 @@ public class HazelcastClusterTest { settings.set(CLUSTER_NODE_HOST, InetAddress.getLoopbackAddress().getHostAddress()); AppStateListener listener = mock(AppStateListener.class); - try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(settings)) { + try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(settings)) { appStateCluster.addListener(listener); HazelcastInstance hzInstance = createHazelcastClient(appStateCluster.getHazelcastCluster()); @@ -277,18 +277,17 @@ public class HazelcastClusterTest { memoryAppender.start(); lc.getLogger("com.hazelcast").addAppender(memoryAppender); - try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(newApplicationSettings())) { + try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(newApplicationSettings())) { } assertThat(memoryAppender.events).isNotEmpty(); memoryAppender.events.stream().forEach( - e -> assertThat(e.getLoggerName()).startsWith("com.hazelcast") - ); + e -> assertThat(e.getLoggerName()).startsWith("com.hazelcast")); } @Test public void removing_the_last_application_node_must_clear_web_leader() throws InterruptedException { - try (AppStateClusterImpl appStateCluster = new AppStateClusterImpl(newSearchSettings())) { + try (ClusterAppStateImpl appStateCluster = new ClusterAppStateImpl(newSearchSettings())) { TestAppSettings appSettings = newApplicationSettings(); appSettings.set(CLUSTER_HOSTS, appStateCluster.getHazelcastCluster().getLocalEndPoint()); appSettings.set(CLUSTER_NODE_PORT, "9004"); diff --git a/server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastClusterTestHelper.java b/server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastClusterTestHelper.java index b1a03efba0b..0345c5bf672 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastClusterTestHelper.java +++ b/server/sonar-main/src/test/java/org/sonar/application/cluster/HazelcastClusterTestHelper.java @@ -51,7 +51,7 @@ public class HazelcastClusterTestHelper { return hazelcastInstance; } - static HazelcastInstance createHazelcastClient(AppStateClusterImpl appStateCluster) { + static HazelcastInstance createHazelcastClient(ClusterAppStateImpl appStateCluster) { return createHazelcastClient(appStateCluster.getHazelcastCluster()); } diff --git a/server/sonar-main/src/test/java/org/sonar/application/health/SearchNodeHealthProviderTest.java b/server/sonar-main/src/test/java/org/sonar/application/health/SearchNodeHealthProviderTest.java index 0bc57d85f38..ae3084db84f 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/health/SearchNodeHealthProviderTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/health/SearchNodeHealthProviderTest.java @@ -19,8 +19,218 @@ */ package org.sonar.application.health; -// TODO implement UT for SearchNodeHealthProviderTest when Daniel and Eric's branch in merged into master +import java.util.Properties; +import java.util.Random; +import javax.annotation.Nullable; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.sonar.NetworkUtils; +import org.sonar.api.utils.System2; +import org.sonar.application.cluster.ClusterAppState; +import org.sonar.cluster.ClusterProperties; +import org.sonar.cluster.health.NodeHealth; +import org.sonar.process.ProcessId; +import org.sonar.process.Props; + +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; +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 SearchNodeHealthProviderTest { + @Rule + public ExpectedException expectedException = ExpectedException.none(); + + private final Random random = new Random(); + private System2 system2 = mock(System2.class); + private NetworkUtils networkUtils = mock(NetworkUtils.class); + private ClusterAppState clusterAppState = mock(ClusterAppState.class); + + @Test + public void constructor_throws_IAE_if_property_node_name_is_not_set() { + Props props = new Props(new Properties()); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Missing property: sonar.cluster.node.name"); + + new SearchNodeHealthProvider(props, system2, clusterAppState, networkUtils); + } + + @Test + public void constructor_throws_NPE_if_NetworkUtils_getHostname_returns_null_and_property_is_not_set() { + Properties properties = new Properties(); + properties.put(ClusterProperties.CLUSTER_NODE_NAME, randomAlphanumeric(3)); + Props props = new Props(properties); + + expectedException.expect(NullPointerException.class); + + new SearchNodeHealthProvider(props, system2, clusterAppState, networkUtils); + } + + @Test + public void constructor_throws_IAE_if_property_node_port_is_not_set() { + Properties properties = new Properties(); + properties.put(ClusterProperties.CLUSTER_NODE_NAME, randomAlphanumeric(3)); + when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(34)); + Props props = new Props(properties); + + expectedException.expect(IllegalArgumentException.class); + expectedException.expectMessage("Missing property: sonar.cluster.node.port"); + + new SearchNodeHealthProvider(props, system2, clusterAppState, networkUtils); + } + + @Test + public void constructor_throws_FormatException_if_property_node_port_is_not_an_integer() { + String port = randomAlphanumeric(3); + Properties properties = new Properties(); + properties.put(ClusterProperties.CLUSTER_NODE_NAME, randomAlphanumeric(3)); + properties.put(ClusterProperties.CLUSTER_NODE_PORT, port); + when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(34)); + Props props = new Props(properties); + + expectedException.expect(NumberFormatException.class); + expectedException.expectMessage("For input string: \"" + port + "\""); + + new SearchNodeHealthProvider(props, system2, clusterAppState, networkUtils); + } + + @Test + public void get_returns_name_and_port_from_properties_at_constructor_time() { + String name = randomAlphanumeric(3); + int port = 1 + random.nextInt(4); + Properties properties = new Properties(); + properties.setProperty(CLUSTER_NODE_NAME, name); + properties.setProperty(CLUSTER_NODE_PORT, valueOf(port)); + when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(34)); + when(system2.now()).thenReturn(1L + random.nextInt(87)); + SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), system2, clusterAppState, networkUtils); + + NodeHealth nodeHealth = underTest.get(); + + assertThat(nodeHealth.getDetails().getName()).isEqualTo(name); + assertThat(nodeHealth.getDetails().getPort()).isEqualTo(port); + + // change values in properties + properties.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(6)); + properties.setProperty(CLUSTER_NODE_PORT, valueOf(1 + random.nextInt(99))); + + 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(55); + Properties properties = new Properties(); + properties.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3)); + properties.setProperty(CLUSTER_NODE_PORT, valueOf(1 + random.nextInt(4))); + properties.setProperty(CLUSTER_NODE_HOST, host); + when(system2.now()).thenReturn(1L + random.nextInt(87)); + SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), system2, clusterAppState, networkUtils); + + NodeHealth nodeHealth = underTest.get(); + + assertThat(nodeHealth.getDetails().getHost()).isEqualTo(host); + + // change now + properties.setProperty(CLUSTER_NODE_HOST, randomAlphanumeric(96)); + + NodeHealth newNodeHealth = underTest.get(); + + assertThat(newNodeHealth.getDetails().getHost()).isEqualTo(host); + } + + @Test + public void get_returns_host_from_NetworkUtils_getHostname_if_property_is_not_set_at_constructor_time() { + getReturnsHostFromNetworkUtils(null); + } + + @Test + public void get_returns_host_from_NetworkUtils_getHostname_if_property_is_empty_at_constructor_time() { + getReturnsHostFromNetworkUtils(random.nextBoolean() ? "" : " "); + } + + private void getReturnsHostFromNetworkUtils(@Nullable String hostPropertyValue) { + String host = randomAlphanumeric(34); + Properties properties = new Properties(); + properties.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3)); + properties.setProperty(CLUSTER_NODE_PORT, valueOf(1 + random.nextInt(4))); + if (hostPropertyValue != null) { + properties.setProperty(CLUSTER_NODE_HOST, hostPropertyValue); + } + when(system2.now()).thenReturn(1L + random.nextInt(87)); + when(networkUtils.getHostname()).thenReturn(host); + SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), system2, clusterAppState, networkUtils); + + NodeHealth nodeHealth = underTest.get(); + + assertThat(nodeHealth.getDetails().getHost()).isEqualTo(host); + + // change now + when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(96)); + + NodeHealth newNodeHealth = underTest.get(); + + assertThat(newNodeHealth.getDetails().getHost()).isEqualTo(host); + } + + @Test + public void get_returns_started_from_System2_now_at_constructor_time() { + Properties properties = new Properties(); + long now = setRequiredPropertiesAndMocks(properties); + SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), system2, clusterAppState, networkUtils); + + NodeHealth nodeHealth = underTest.get(); + + assertThat(nodeHealth.getDetails().getStarted()).isEqualTo(now); + + // change now + when(system2.now()).thenReturn(now); + + NodeHealth newNodeHealth = underTest.get(); + + assertThat(newNodeHealth.getDetails().getStarted()).isEqualTo(now); + } + + @Test + public void get_returns_status_GREEN_if_elasticsearch_process_is_operational_in_ClusterAppState() { + Properties properties = new Properties(); + setRequiredPropertiesAndMocks(properties); + when(clusterAppState.isOperational(ProcessId.ELASTICSEARCH, true)).thenReturn(true); + SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), system2, clusterAppState, networkUtils); + + NodeHealth nodeHealth = underTest.get(); + + assertThat(nodeHealth.getStatus()).isEqualTo(NodeHealth.Status.GREEN); + } + + @Test + public void get_returns_status_RED_with_cause_if_elasticsearch_process_is_not_operational_in_ClusterAppState() { + Properties properties = new Properties(); + setRequiredPropertiesAndMocks(properties); + when(clusterAppState.isOperational(ProcessId.ELASTICSEARCH, true)).thenReturn(false); + SearchNodeHealthProvider underTest = new SearchNodeHealthProvider(new Props(properties), system2, clusterAppState, networkUtils); + + NodeHealth nodeHealth = underTest.get(); + assertThat(nodeHealth.getStatus()).isEqualTo(NodeHealth.Status.RED); + assertThat(nodeHealth.getCauses()).containsOnly("Elasticsearch is not operational"); + } + private long setRequiredPropertiesAndMocks(Properties properties) { + properties.setProperty(CLUSTER_NODE_NAME, randomAlphanumeric(3)); + properties.setProperty(CLUSTER_NODE_PORT, valueOf(1 + random.nextInt(4))); + long now = 1L + random.nextInt(87); + when(system2.now()).thenReturn(now); + when(networkUtils.getHostname()).thenReturn(randomAlphanumeric(34)); + return now; + } } 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 9f4ec3ebe7a..43e0db25c30 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 @@ -132,7 +132,7 @@ public class HealthCheckerImplTest { @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); + HealthCheckerImpl underTest = new HealthCheckerImpl(webServer, new NodeHealthCheck[0], new ClusterHealthCheck[0], null); expectedException.expect(IllegalStateException.class); expectedException.expectMessage("HealthState instance can't be null when clustering is enabled"); |